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
130 changes: 130 additions & 0 deletions .github/workflows/core-main-artifact-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Check chdb-core main artifacts

# A one-off, pre-release consumer check. The normal Rust workflow continues to
# test its release pin; this one tests four CI artifacts from the same core SHA.
on:
pull_request:
branches: [main]
paths:
- .github/workflows/core-main-artifact-check.yml
- tests/bundled_runtime_hidden.rs
- tests/runtime_faces.rs

permissions:
contents: read

jobs:
main_artifacts:
if: github.head_ref == 'codex/chdb-core-26.9.2-rc.2'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
run_id: '38427250828'
artifact: chdb-artifacts-linux-x86_64
asset_prefix: linux-x86_64
- os: ubuntu-24.04-arm
run_id: '38429301339'
artifact: chdb-artifacts-linux-aarch64
asset_prefix: linux-aarch64
- os: macos-latest
run_id: '38427008431'
artifact: chdb-artifacts-macos-arm64
asset_prefix: macos-arm64
test_min_macos: '12.0'
- os: macos-15-intel
run_id: '38428913578'
artifact: chdb-artifacts-macos-x86_64
asset_prefix: macos-x86_64
test_min_macos: '12.0'
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
CORE_SHA: dec391dcdf788cbecede338a0f305b399d8495e1
CORE_RUN_ID: ${{ matrix.run_id }}
CORE_ARTIFACT: ${{ matrix.artifact }}
ASSET_PREFIX: ${{ matrix.asset_prefix }}
CHDB_TEST_MIN_MACOS: ${{ matrix.test_min_macos }}
steps:
- uses: actions/checkout@v4

- name: Verify source and fetch the exact CI artifact
env:
# A read-only fine-grained token for chdb-core Actions can be added
# as this secret if the Rust repository's token cannot read it.
GH_TOKEN: ${{ secrets.CHDB_CORE_ARTIFACT_READ_TOKEN || github.token }}
run: |
set -euo pipefail
actual=$(gh run view "$CORE_RUN_ID" -R chdb-io/chdb-core \
--json headSha,conclusion,headBranch \
--jq '[.headSha,.conclusion,.headBranch] | join(" ")')
if [ "$actual" != "$CORE_SHA success main" ]; then
echo "::error::core run $CORE_RUN_ID is '$actual', expected '$CORE_SHA success main'"
exit 1
fi
gh run download "$CORE_RUN_ID" -R chdb-io/chdb-core \
-n "$CORE_ARTIFACT" -D "$RUNNER_TEMP/core-artifact"

- name: Unpack matching native libraries and headers
run: |
set -euo pipefail
DYNAMIC_DIR="$RUNNER_TEMP/core-dynamic"
STATIC_DIR="$RUNNER_TEMP/core-static"
mkdir -p "$DYNAMIC_DIR" "$STATIC_DIR"
tar -xzf "$RUNNER_TEMP/core-artifact/$ASSET_PREFIX-libchdb.tar.gz" -C "$DYNAMIC_DIR"
tar -xzf "$RUNNER_TEMP/core-artifact/$ASSET_PREFIX-libchdb-static.tar.gz" -C "$STATIC_DIR"
test -s "$DYNAMIC_DIR/libchdb.so"
test -s "$STATIC_DIR/libchdb.a"
cmp "$DYNAMIC_DIR/chdb.h" "$STATIC_DIR/chdb.h"
echo "Using chdb-core $CORE_SHA: $CORE_ARTIFACT from run $CORE_RUN_ID"

- name: Check formatting and dynamic Clippy
if: matrix.os == 'ubuntu-latest'
env:
CHDB_LIB_DIR: ${{ runner.temp }}/core-dynamic
CHDB_INCLUDE_DIR: ${{ runner.temp }}/core-dynamic
run: |
cargo fmt --check
cargo clippy --all-targets -- --deny warnings

- name: Build and test dynamic library
env:
CHDB_LIB_DIR: ${{ runner.temp }}/core-dynamic
CHDB_INCLUDE_DIR: ${{ runner.temp }}/core-dynamic
LD_LIBRARY_PATH: ${{ runner.temp }}/core-dynamic
DYLD_LIBRARY_PATH: ${{ runner.temp }}/core-dynamic
RUST_BACKTRACE: full
run: |
cargo build --verbose
cargo test -- --test-threads=1
cargo test --features durable -- --test-threads=1
if [ "$RUNNER_OS" = Linux ] && [ "$RUNNER_ARCH" = X64 ]; then
cargo test --features durable-s3 --lib -- --test-threads=1 durable::backends
fi

- name: Check all-feature Clippy against the static archive
if: matrix.os == 'ubuntu-latest'
env:
CHDB_LIB_DIR: ${{ runner.temp }}/core-static
CHDB_INCLUDE_DIR: ${{ runner.temp }}/core-static
run: cargo clippy --all-targets --all-features -- --deny warnings

- name: Build, inspect and run statically linked tests
env:
CHDB_LIB_DIR: ${{ runner.temp }}/core-static
CHDB_INCLUDE_DIR: ${{ runner.temp }}/core-static
CHDB_CHECK_HOST_UNWIND: '1'
RUST_BACKTRACE: full
run: |
set -euo pipefail
cargo test --features static --no-run --tests -j 2 --message-format=json > "$RUNNER_TEMP/artifacts.json"
jq -r 'select(.reason == "compiler-artifact" and .profile.test == true and .executable != null)
| .executable' "$RUNNER_TEMP/artifacts.json" > "$RUNNER_TEMP/test-binaries.txt"
test -s "$RUNNER_TEMP/test-binaries.txt"
xargs .github/scripts/check-static-link.sh < "$RUNNER_TEMP/test-binaries.txt"
while read -r binary <&3; do
echo "== $binary"
"$binary" --test-threads=1 < /dev/null
done 3< "$RUNNER_TEMP/test-binaries.txt"
cargo test --features static --doc
66 changes: 42 additions & 24 deletions tests/bundled_runtime_hidden.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//! The bundled C++ runtime must not be visible from outside the archive.
//! The bundled C++ runtime must not capture symbols used by the host runtime.
//!
//! This is the cause the runtime cases in `runtime_faces.rs` observe the effect
//! of, checked directly and without linking anything.
//! The runtime cases in `runtime_faces.rs` check the effect in a linked binary;
//! this checks the archive's symbols directly.
//!
//! `libchdb.so` / `.dylib` gate their exports at link time, with a version
//! script on Linux and an exported-symbols list on macOS, which is why dynamic
//! linking never had this problem. An archive has no link step of its own: every
//! symbol it carries reaches the final artifact and takes part in resolution. So
//! for a static build the gate has to be compiled into the objects, and this
//! asserts that it was — that the three bundled runtime targets have no
//! externally visible definitions at all.
//! for a static build the gate must be baked into the archive. Older archives
//! hide the bundled runtime's definitions, while newer ones prelink and localize
//! them on Linux or rename them on macOS.
//!
//! Two reasons to check the cause and not only the effect. It holds on Linux,
//! where the visibility can regress with no symptom to observe, so no runtime
Expand All @@ -20,9 +20,8 @@
//! over its own build output (`chdb/build/check_static_lib_hermetic.sh`, gate
//! 2). That one guards the release; this one guards what actually arrived.
//!
//! Measured across the engines this crate has been pinned to: v26.7.0 shows
//! 2365 externally visible definitions among 58 runtime members, and
//! v26.7.2-rc.2 and v26.7.3 show 0 among the same 58.
//! Measured across older engines: v26.7.0 shows 2365 externally visible
//! definitions among 58 runtime members, and v26.7.2-rc.2/v26.7.3 show 0.

mod common;

Expand All @@ -37,7 +36,7 @@ const RUNTIME_MEMBER_PREFIXES: [&str; 3] = ["libcxx__", "libcxxabi__", "libunwin
const OFFENDERS_SHOWN: usize = 20;

#[test]
fn bundled_runtime_objects_export_nothing() {
fn bundled_runtime_symbols_cannot_bind_host() {
let artifact = Path::new(env!("CHDB_LINKED_ARTIFACT"));
assert!(
!artifact.as_os_str().is_empty(),
Expand All @@ -54,13 +53,33 @@ fn bundled_runtime_objects_export_nothing() {
}

let members = runtime_members(artifact);
// Zero would mean the naming changed and this test had quietly stopped
// testing anything, which is worse than a failure.
assert!(
!members.is_empty(),
"no {RUNTIME_MEMBER_PREFIXES:?} members among the archive's members: either the \
bundled runtime is no longer a separate set of objects, or chdb-core renamed them"
);
if members.is_empty() {
// New Linux archives prelink all objects into one member and localize
// everything outside the C API. The old per-runtime-member check no
// longer describes that layout; verify its exported surface here and
// exercise the host unwinder in runtime_faces.
let all_members = run("ar", &[OsStr::new("t"), artifact.as_os_str()], None);
let names: Vec<_> = all_members.lines().collect();
assert!(
cfg!(target_os = "linux")
&& names.len() == 1
&& names[0].starts_with("libchdb")
&& names[0].ends_with(".o"),
"unknown static archive layout: {names:?}"
);
let (defined, visible) = symbols(&[artifact.to_path_buf()]);
assert!(defined > 0 && visible.iter().any(|name| name == "chdb_connect"));
assert!(
visible.len() <= 100
&& !visible.iter().any(|name| {
name.starts_with("_Unwind_")
|| name.starts_with("unw_")
|| name.starts_with("__cxa_")
}),
"prelinked archive exports unexpected runtime symbols: {visible:?}"
);
return;
}

let scratch = common::tempdir();
let objects = extract(artifact, &members, scratch.path());
Expand Down Expand Up @@ -91,15 +110,14 @@ fn bundled_runtime_objects_export_nothing() {
}
}

// Asserted at zero rather than "disjoint from this host's system runtime".
// These targets are built to have no externally visible definitions at all,
// and a symbol that merely happens to be absent from the running OS version
// would otherwise slip through.
// The old layout hides these definitions; the new macOS layout renames
// every one so a host can never resolve a runtime reference to it.
assert!(
visible.is_empty(),
visible.is_empty()
|| (cfg!(target_os = "macos") && visible.iter().all(|s| s.ends_with(".chdb"))),
"{} of {} definitions in the bundled runtime are visible from outside the \
archive; a static build can then bind a call made inside the engine to \
another copy of the C++ runtime",
archive under an unrenamed name; a static build can then bind a host \
runtime call to the bundled C++ runtime",
visible.len(),
defined
);
Expand Down
34 changes: 34 additions & 0 deletions tests/runtime_faces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ fn cases() -> Vec<Case> {
run: arrow_release_callback,
});

// chdb-io/chdb-rust#53: an archive may keep the engine working while
// breaking the host Rust runtime's unwinder. Exercise the real consumer
// after a failing engine query, isolated so a SIGSEGV cannot hide the
// outcome of the other runtime cases.
#[cfg(feature = "static")]
if std::env::var_os("CHDB_CHECK_HOST_UNWIND").is_some() {
// The current pin predates chdb-core's fix. Opt in when validating a
// candidate archive, and enable by default only after adopting it.
cases.push(Case {
name: "host_backtrace_and_panic",
expect: "backtrace=ok|panic=ok\n",
run: host_backtrace_and_panic,
});
}

cases
}

Expand Down Expand Up @@ -489,6 +504,25 @@ fn signal_handlers_before_connect() {
println!("ok");
}

#[cfg(feature = "static")]
fn host_backtrace_and_panic() {
chdb_rust::runtime::signal_handlers(false);
let conn = Connection::open_in_memory().expect("open");
assert!(conn
.query("SELECT * FROM chdb_rust_missing_table", OutputFormat::CSV)
.is_err());

let trace = std::backtrace::Backtrace::force_capture();
assert_eq!(trace.status(), std::backtrace::BacktraceStatus::Captured);
assert!(!trace.to_string().trim().is_empty());
print!("backtrace=ok|");

std::panic::set_hook(Box::new(|_| {}));
assert!(std::panic::catch_unwind(|| panic!("expected panic")).is_err());
drop(conn);
println!("panic=ok");
}

/// The deadly-signal disposition for `sig`, read via `sigaction` with a null
/// `act` — a query, not a change.
fn signal_disposition(sig: libc::c_int) -> libc::sighandler_t {
Expand Down
Loading