Skip to content

Commit 35c3d97

Browse files
committed
Replace Valgrind check with sanitizers
1 parent beca6f8 commit 35c3d97

6 files changed

Lines changed: 197 additions & 19 deletions

File tree

.github/workflows/tests.yml

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -222,30 +222,27 @@ jobs:
222222
working-directory: dart
223223
run: dart test
224224

225-
valgrind:
226-
name: Testing with Valgrind on ${{ matrix.os }}
227-
runs-on: ${{ matrix.os }}
228-
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)
229-
strategy:
230-
matrix:
231-
include:
232-
- os: ubuntu-latest
225+
test_with_sanitizers:
226+
runs-on: ubuntu-latest
233227
steps:
234228
- uses: actions/checkout@v6
235229
with:
236230
persist-credentials: false
237-
- name: Install Rust
231+
- uses: dart-lang/setup-dart@v1
232+
- name: Install Rust Nightly
238233
run: rustup install
234+
- name: Install LLVM toolchain
235+
run: |
236+
sudo apt update
237+
sudo apt install -y llvm clang lld
239238
240-
- name: Install valgrind
241-
run: sudo apt update && sudo apt install -y valgrind
239+
- name: Build with sanitizers
240+
run: tool/build_linux_sanitized.sh
242241

243-
- name: Install Cargo Valgrind
244-
# TODO: Use released version. Currently we rely on the git version while we wait for this
245-
# to be released: https://github.com/jfrimmel/cargo-valgrind/commit/408c0b4fb56e84eddc2bb09c88a11ba3adc0c188
246-
run: cargo install --git https://github.com/jfrimmel/cargo-valgrind cargo-valgrind
247-
#run: cargo install cargo-valgrind
242+
- name: Test with MemorySanitizer
243+
working-directory: dart
244+
run: dart tool/run_tests.dart msan
248245

249-
- name: Test Core
250-
run: |
251-
cargo valgrind test -p powersync_core
246+
- name: Test with AddressSanitizer
247+
working-directory: dart
248+
run: dart tool/run_tests.dart asan

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ target/
1010
*.tar.xz
1111
*.zip
1212
.build
13+
/sanitized/

dart/test/utils/native_test_utils.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:ffi';
22
import 'dart:io';
3+
import 'dart:math';
34

45
import 'package:clock/clock.dart';
56
import 'package:fake_async/fake_async.dart';
@@ -14,12 +15,23 @@ const defaultSqlitePath = 'libsqlite3.so.0';
1415
const cargoDebugPath = '../target/debug';
1516
var didLoadExtension = false;
1617

18+
String? testingWithSanitizers = null;
19+
1720
CommonDatabase openTestDatabase(
1821
{VirtualFileSystem? vfs, String fileName = ':memory:'}) {
1922
if (!didLoadExtension) {
2023
loadExtension();
2124
}
2225

26+
if (testingWithSanitizers != null && vfs == null) {
27+
final inMemory =
28+
InMemoryFileSystem(name: 'in-memory-${Random().nextInt(1 << 32)}');
29+
sqlite3.registerVirtualFileSystem(inMemory);
30+
addTearDown(() => sqlite3.unregisterVirtualFileSystem(inMemory));
31+
32+
vfs = inMemory;
33+
}
34+
2335
final db = sqlite3.open(fileName, vfs: vfs?.name);
2436
addTearDown(db.close);
2537
return db;
@@ -64,6 +76,10 @@ String resolvePowerSyncLibrary() {
6476
}
6577

6678
String _getLibraryForPlatform({String? path = cargoDebugPath}) {
79+
if (testingWithSanitizers case final sanitizer?) {
80+
return '../sanitized/core_extension/libpowersync_$sanitizer.linux.so';
81+
}
82+
6783
return switch (Abi.current()) {
6884
Abi.androidArm ||
6985
Abi.androidArm64 ||

dart/tool/all_tests.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'package:test/test.dart';
2+
3+
import '../test/crud_test.dart' as crud_test;
4+
import '../test/error_test.dart' as error_test;
5+
import '../test/js_key_encoding_test.dart' as json_key_encoding_test;
6+
import '../test/migration_test.dart' as migration_test;
7+
import '../test/schema_test.dart' as schema_test;
8+
// Skipping sync_local_performance_test because it's slow. It's functionality is
9+
// covered by other tests.
10+
import '../test/sync_stream_test.dart' as sync_stream_test;
11+
import '../test/update_hooks_test.dart' as update_hooks_test;
12+
13+
import '../test/utils/native_test_utils.dart';
14+
15+
/// Runs all native tests.
16+
///
17+
/// We aot-compile this file to run tests with different sanitizers.
18+
void main(List<String> args) {
19+
testingWithSanitizers = args.single;
20+
21+
group('crud_test.dart', crud_test.main);
22+
group('error_test.dart', error_test.main);
23+
group('js_key_encoding_test.dart', json_key_encoding_test.main);
24+
group('migration_test.dart', migration_test.main);
25+
group('schema_test.dart', schema_test.main);
26+
group('sync_stream_test.dart', sync_stream_test.main);
27+
group('update_hooks_test.dart', update_hooks_test.main);
28+
}

dart/tool/run_tests.dart

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import 'dart:io';
2+
3+
import 'package:path/path.dart' as p;
4+
5+
/// Runs `all_tests.dart` as a single AOT-compiled executable with sanitizers
6+
/// enabled.
7+
///
8+
/// To run tests with a sanitizer, use `dart tool/run_tests.dart
9+
/// --sanitizer $sanitizer`, where `$sanitizer` is either `asan` or `msan`.
10+
/// Note that sanitizers are only supported on X64 Linux hosts.
11+
///
12+
/// Running tests with sanitizers also requires an instrumented build of SQLite
13+
/// and the core extension, which can be built with
14+
/// `tool/build_linux_sanitized.sh`.
15+
void main(List<String> args) async {
16+
Never invalidArgs() {
17+
print('Usage: dart tool/run_tests.dart asan|msan');
18+
exit(1);
19+
}
20+
21+
if (args.length != 1) {
22+
invalidArgs();
23+
}
24+
25+
final sanitizer = args.single;
26+
if (sanitizer != 'asan' && sanitizer != 'msan') invalidArgs();
27+
final expandedName = switch (sanitizer) {
28+
'asan' => 'address',
29+
'msan' => 'memory',
30+
_ => throw AssertionError(),
31+
};
32+
33+
final dir = await Directory.systemTemp.createTemp('core-extension-tests');
34+
final aotPath = p.join(dir.path, 'test.aot');
35+
final assetsConfig = await _createNativeAssetsConfig(dir, expandedName);
36+
37+
try {
38+
print('AOT-compiling tests');
39+
final result = await Process.run(Platform.executable, [
40+
'compile',
41+
'aot-snapshot',
42+
'tool/all_tests.dart',
43+
'--output',
44+
aotPath,
45+
'--target-sanitizer=$sanitizer',
46+
'--extra-gen-kernel-options=--native-assets=${assetsConfig.path}',
47+
]);
48+
49+
if (result.exitCode != 0 || !await File(aotPath).exists()) {
50+
throw '''
51+
could not compile test script
52+
53+
exitCode: ${result.exitCode}
54+
stdout: ${result.stdout}
55+
stderr: ${result.stderr}
56+
''';
57+
}
58+
59+
var runtimeName = 'dartaotruntime_$sanitizer';
60+
61+
print('Running with $runtimeName');
62+
final runtime = p.join(p.dirname(Platform.resolvedExecutable), runtimeName);
63+
final process = await Process.start(
64+
runtime,
65+
[
66+
aotPath,
67+
expandedName,
68+
],
69+
mode: ProcessStartMode.inheritStdio);
70+
final exit = await process.exitCode;
71+
if (exit != 0) {
72+
throw 'Expected exit code 0, got $exit';
73+
}
74+
} finally {
75+
await dir.delete(recursive: true);
76+
}
77+
}
78+
79+
Future<File> _createNativeAssetsConfig(
80+
Directory tmpForRun,
81+
String? expandedName,
82+
) async {
83+
final sqliteFile = p.normalize(
84+
p.absolute('../sanitized/sqlite', 'libsqlite3_$expandedName.so'),
85+
);
86+
87+
final yaml = '''
88+
format-version: [1, 0, 0]
89+
native-assets:
90+
linux_x64:
91+
"package:sqlite3/src/ffi/libsqlite3.g.dart":
92+
- absolute
93+
- "$sqliteFile"
94+
''';
95+
96+
final file = File(p.join(tmpForRun.path, 'assets.yaml'));
97+
await file.writeAsString(yaml);
98+
return file;
99+
}

tool/build_linux_sanitized.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash
2+
set -e
3+
4+
function compile_core() {
5+
local sanitizer=$1
6+
7+
RUSTDOCFLAGS="-Zsanitizer=$sanitizer" RUSTFLAGS="-Zsanitizer=$sanitizer" cargo build \
8+
-p powersync_loadable \
9+
-Z build-std=panic_abort,core,alloc \
10+
--features nightly \
11+
--release \
12+
--target x86_64-unknown-linux-gnu
13+
14+
mv "target/x86_64-unknown-linux-gnu/release/libpowersync.so" "sanitized/core_extension/libpowersync_$sanitizer.linux.so"
15+
}
16+
17+
function compile_sqlite() {
18+
local sanitizer=$1
19+
20+
clang -O3 -fsanitize=$sanitizer -fno-omit-frame-pointer -fuse-ld=lld -fPIC \
21+
-DSQLITE_ENABLE_API_ARMOR=1 \
22+
-DSQLITE_OMIT_DEPRECATED \
23+
-DSQLITE_DQS=0 \
24+
-DSQLISQLITE_ENABLE_DBSTAT_VTABTE_ENABLE_FTS5 \
25+
-DSQLITE_ENABLE_STMTVTAB \
26+
-shared \
27+
crates/sqlite/sqlite/sqlite3.c \
28+
-o sanitized/sqlite/libsqlite3_$sanitizer.so
29+
}
30+
31+
mkdir -p sanitized/core_extension
32+
compile_core address
33+
compile_core memory
34+
35+
mkdir -p sanitized/sqlite
36+
compile_sqlite address
37+
compile_sqlite memory

0 commit comments

Comments
 (0)