Skip to content

Commit 52c72c4

Browse files
committed
add CI workflow for Ruby ASAN (AddressSanitizer) testing
Use `ruby/setup-ruby` with `ruby-version: asan` on ubuntu-24.04. The LD_PRELOAD libstdc++.so to resolve `__cxa_throw` ASAN CHECK failure when DuckDB throws C++ exceptions internally. See: google/sanitizers#934
1 parent 7e84bf5 commit 52c72c4

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Ruby ASAN
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
types:
9+
- opened
10+
- synchronize
11+
- reopened
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
test:
19+
runs-on: ubuntu-24.04
20+
timeout-minutes: 120
21+
strategy:
22+
matrix:
23+
duckdb: ['1.4.4', '1.3.2']
24+
25+
steps:
26+
- uses: actions/checkout@v6
27+
28+
- name: Set up Ruby with ASAN
29+
uses: ruby/setup-ruby@v1
30+
with:
31+
ruby-version: asan
32+
bundler-cache: true
33+
34+
- name: duckdb cache
35+
id: duckdb-cache
36+
uses: actions/cache@v5
37+
with:
38+
path: duckdb-v${{ matrix.duckdb }}
39+
key: ${{ runner.os }}-duckdb-v${{ matrix.duckdb }}
40+
41+
- name: Build duckdb ${{ matrix.duckdb }}
42+
env:
43+
DUCKDB_VERSION: ${{ matrix.duckdb }}
44+
if: steps.duckdb-cache.outputs.cache-hit != 'true'
45+
run: |
46+
git clone -b v$DUCKDB_VERSION https://github.com/duckdb/duckdb.git duckdb-tmp-v$DUCKDB_VERSION
47+
cd duckdb-tmp-v$DUCKDB_VERSION && make && cd ..
48+
rm -rf duckdb-v$DUCKDB_VERSION
49+
mkdir -p duckdb-v$DUCKDB_VERSION/build/release/src duckdb-v$DUCKDB_VERSION/src
50+
cp -rip duckdb-tmp-v$DUCKDB_VERSION/build/release/src/*.so duckdb-v$DUCKDB_VERSION/build/release/src
51+
cp -rip duckdb-tmp-v$DUCKDB_VERSION/src/include duckdb-v$DUCKDB_VERSION/src/
52+
53+
- name: bundle install
54+
env:
55+
DUCKDB_VERSION: ${{ matrix.duckdb }}
56+
run: |
57+
bundle install --jobs 4 --retry 3
58+
59+
- name: Build with ASAN Ruby
60+
env:
61+
DUCKDB_VERSION: ${{ matrix.duckdb }}
62+
run: |
63+
bundle exec rake build -- --with-duckdb-include=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/src/include --with-duckdb-lib=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/build/release/src/
64+
65+
- name: Run tests with ASAN
66+
env:
67+
DUCKDB_VERSION: ${{ matrix.duckdb }}
68+
run: |
69+
# LD_PRELOAD resolves __cxa_throw ASAN CHECK failure when DuckDB (built
70+
# without ASAN) throws C++ exceptions internally.
71+
# See: https://github.com/google/sanitizers/issues/934
72+
export LD_PRELOAD="$(realpath "$(gcc -print-file-name=libstdc++.so)")"
73+
rake test
74+
75+
post-test:
76+
name: All ASAN tests passed
77+
runs-on: ubuntu-latest
78+
needs: test
79+
steps:
80+
- run: echo ok

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
All notable changes to this project will be documented in this file.
44

55
# Unreleased
6+
- add CI workflow for Ruby ASAN (AddressSanitizer) testing.
67
- DuckDB::BindInfo#add_result_column accepts symbols as column, column type argument.
78
- add `DuckDB::LogicalType.resolve`.
89
- add `DuckDB.cast`.

test/duckdb_test/asan_test.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
# Tests that exercise error paths to verify memory safety under ASAN.
6+
# These tests ensure that DuckDB's internal C++ exceptions (caught by the C API)
7+
# do not cause __cxa_throw ASAN CHECK failures with the LD_PRELOAD workaround.
8+
module DuckDBTest
9+
class AsanTest < Minitest::Test
10+
def test_open_database_with_invalid_path
11+
assert_raises(DuckDB::Error) do
12+
DuckDB::Database.open('not_exist_dir/not_exist_file')
13+
end
14+
end
15+
16+
def test_prepare_invalid_sql
17+
db = DuckDB::Database.open
18+
con = db.connect
19+
assert_raises(DuckDB::Error) do
20+
DuckDB::PreparedStatement.new(con, 'INVALID SQL')
21+
end
22+
end
23+
24+
def test_query_nonexistent_table
25+
db = DuckDB::Database.open
26+
con = db.connect
27+
assert_raises(DuckDB::Error) do
28+
con.query('SELECT * FROM nonexistent_table')
29+
end
30+
end
31+
32+
def test_extract_invalid_statements
33+
db = DuckDB::Database.open
34+
con = db.connect
35+
assert_raises(DuckDB::Error) do
36+
DuckDB::ExtractedStatements.new(con, 'INVALID SQL;')
37+
end
38+
end
39+
end
40+
end

0 commit comments

Comments
 (0)