ci: refact android ci (#330)
This commit is contained in:
parent
044a377eee
commit
6737810190
|
|
@ -1,39 +1,56 @@
|
||||||
name: Android Cross Build
|
name: Android Cross Build & Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
api:
|
||||||
|
description: 'Android API level'
|
||||||
|
required: false
|
||||||
|
default: '34'
|
||||||
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
NDK_VERSION: '26.1.10909125'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-android:
|
build-and-test:
|
||||||
# sdkmanager and other Android tools are x86‑only; ARM runners fail with exit code 1
|
|
||||||
# switch back to an x86 image so the setup-android action can install the SDK
|
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
timeout-minutes: 120
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
abi: [x86_64]
|
abi: [x86_64]
|
||||||
api: [21]
|
api: ${{ github.event.inputs.api && fromJSON(format('["{0}"]', github.event.inputs.api)) || fromJSON('["34"]') }}
|
||||||
|
env:
|
||||||
|
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||||
|
CCACHE_NOHASHDIR: '1'
|
||||||
|
CCACHE_SLOPPINESS: clang_index_store,file_stat_matches,include_file_mtime,locale,time_macros
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
# ── Environment setup ──────────────────────────────────────────────
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Cache dependencies
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
with:
|
||||||
path: |
|
submodules: recursive
|
||||||
~/.ccache
|
|
||||||
key: ${{ runner.os }}-dependencies-cache-${{ hashFiles('**/CMakeLists.txt', 'thirdparty/**') }}-stl-fix
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y --no-install-recommends \
|
sudo apt-get install -y --no-install-recommends \
|
||||||
cmake ninja-build git ca-certificates python3 \
|
cmake ninja-build git ca-certificates python3 \
|
||||||
build-essential make ccache
|
build-essential make ccache unzip curl
|
||||||
|
|
||||||
|
- name: Cache ccache
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: ~/.ccache
|
||||||
|
key: ${{ runner.os }}-ccache-android-${{ matrix.abi }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-ccache-android-${{ matrix.abi }}-
|
||||||
|
|
||||||
- name: Setup Java 17
|
- name: Setup Java 17
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
|
|
@ -44,143 +61,372 @@ jobs:
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v4
|
uses: android-actions/setup-android@v4
|
||||||
|
|
||||||
- name: Install NDK (side by side)
|
- name: Enable KVM
|
||||||
|
run: sudo chmod 666 /dev/kvm || true
|
||||||
|
|
||||||
|
- name: Install NDK, emulator and system image
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sdkmanager "ndk;26.1.10909125"
|
sdkmanager --install \
|
||||||
|
"ndk;$NDK_VERSION" \
|
||||||
|
"platform-tools" \
|
||||||
|
"platforms;android-${{ matrix.api }}" \
|
||||||
|
"emulator"
|
||||||
|
|
||||||
- name: Cache host protoc build
|
# Install x86_64 system image (try variants in order of availability)
|
||||||
|
sdkmanager --install "system-images;android-${{ matrix.api }};google_apis;x86_64" 2>/dev/null || \
|
||||||
|
sdkmanager --install "system-images;android-${{ matrix.api }};google_apis_playstore;x86_64" 2>/dev/null || \
|
||||||
|
sdkmanager --install "system-images;android-${{ matrix.api }};default;x86_64"
|
||||||
|
|
||||||
|
# ── Step 1: build host protoc (using HOST compiler, NOT NDK) ───────
|
||||||
|
- name: Cache host protoc
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: build-host
|
path: build_host
|
||||||
key: ${{ runner.os }}-host-protoc-${{ hashFiles('src/**', 'CMakeLists.txt') }}-stl-fix
|
key: ${{ runner.os }}-host-protoc-${{ hashFiles('thirdparty/protobuf/**', 'CMakeLists.txt') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-host-protoc-
|
${{ runner.os }}-host-protoc-
|
||||||
|
|
||||||
- name: Use host env to compile protoc
|
- name: 'Step 1: Build host protoc'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init
|
if [ ! -f "build_host/bin/protoc" ]; then
|
||||||
if [ ! -d "build-host" ]; then
|
git submodule foreach --recursive 'git stash --include-untracked' 2>/dev/null || true
|
||||||
export CCACHE_BASEDIR="$GITHUB_WORKSPACE"
|
cmake -S . -B build_host \
|
||||||
export CCACHE_NOHASHDIR=1
|
|
||||||
export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_mtime,locale,time_macros
|
|
||||||
|
|
||||||
cmake -S . -B build-host -G Ninja \
|
|
||||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
|
||||||
cmake --build build-host --target protoc --parallel
|
|
||||||
else
|
|
||||||
echo "Using cached host protoc build"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Cache Android build
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: build-android-${{ matrix.abi }}
|
|
||||||
key: ${{ runner.os }}-android-build-${{ matrix.abi }}-${{ hashFiles('src/**', 'CMakeLists.txt', 'cmake/**', 'thirdparty/**') }}-stl-fix-3
|
|
||||||
|
|
||||||
- name: Configure and Build
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
git submodule foreach --recursive 'git stash --include-untracked'
|
|
||||||
|
|
||||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
|
||||||
export ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/26.1.10909125"
|
|
||||||
|
|
||||||
export CCACHE_BASEDIR="$GITHUB_WORKSPACE"
|
|
||||||
export CCACHE_NOHASHDIR=1
|
|
||||||
export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_mtime,locale,time_macros
|
|
||||||
|
|
||||||
if [ ! -d "build-android-${{ matrix.abi }}" ]; then
|
|
||||||
cmake -S . -B build-android-${{ matrix.abi }} -G Ninja \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="" \
|
||||||
-DANDROID_ABI=${{ matrix.abi }} \
|
|
||||||
-DANDROID_PLATFORM=android-${{ matrix.api }} \
|
|
||||||
-DANDROID_STL=c++_static \
|
|
||||||
-DBUILD_PYTHON_BINDINGS=OFF \
|
|
||||||
-DENABLE_NATIVE=OFF \
|
|
||||||
-DAUTO_DETECT_ARCH=OFF \
|
|
||||||
-DBUILD_TOOLS=OFF \
|
|
||||||
-DGLOBAL_CC_PROTOBUF_PROTOC="$GITHUB_WORKSPACE/build-host/bin/protoc" \
|
|
||||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
-G Ninja
|
||||||
cmake --build build-android-${{ matrix.abi }} --parallel --verbose
|
cmake --build build_host --target protoc --parallel
|
||||||
else
|
else
|
||||||
echo "Using cached Android build directory"
|
echo "Using cached host protoc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Cache examples build
|
# ── Step 2: cross-compile zvec + tests for Android ─────────────────
|
||||||
uses: actions/cache@v5
|
- name: 'Step 2: Cross-compile zvec and tests'
|
||||||
with:
|
shell: bash
|
||||||
path: examples/c++/build-android-examples-${{ matrix.abi }}
|
env:
|
||||||
key: ${{ runner.os }}-examples-build-${{ matrix.abi }}-${{ hashFiles('examples/c++/**', 'CMakeLists.txt', 'src/**') }}-stl-fix-3
|
BUILD_DIR: build_android_${{ matrix.abi }}
|
||||||
|
run: |
|
||||||
|
ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||||
|
|
||||||
- name: Build examples
|
# Reset thirdparty so the cross toolchain can patch cleanly
|
||||||
|
git submodule foreach --recursive 'git stash --include-untracked' 2>/dev/null || true
|
||||||
|
|
||||||
|
# Force reconfigure to pick up any cmake changes
|
||||||
|
rm -f "$BUILD_DIR/CMakeCache.txt"
|
||||||
|
|
||||||
|
cmake -S . -B "$BUILD_DIR" -G Ninja \
|
||||||
|
-DANDROID_NDK="$ANDROID_NDK_HOME" \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
|
||||||
|
-DANDROID_ABI=${{ matrix.abi }} \
|
||||||
|
-DANDROID_NATIVE_API_LEVEL=${{ matrix.api }} \
|
||||||
|
-DANDROID_STL=c++_static \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_PYTHON_BINDINGS=OFF \
|
||||||
|
-DBUILD_TOOLS=OFF \
|
||||||
|
-DENABLE_NATIVE=OFF \
|
||||||
|
-DAUTO_DETECT_ARCH=OFF \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$BUILD_DIR/install" \
|
||||||
|
-DGLOBAL_CC_PROTOBUF_PROTOC="$GITHUB_WORKSPACE/build_host/bin/protoc" \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
|
||||||
|
echo "Building all targets..."
|
||||||
|
cmake --build "$BUILD_DIR" --parallel
|
||||||
|
|
||||||
|
# Discover test targets from ctest metadata
|
||||||
|
echo "Discovering test targets..."
|
||||||
|
TEST_NAMES=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
name=$(echo "$line" | sed -n 's/.*Test[[:space:]]*#[0-9]*:[[:space:]]*//p')
|
||||||
|
[ -n "$name" ] && TEST_NAMES+=("$name")
|
||||||
|
done < <(cd "$BUILD_DIR" && ctest --show-only 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Fallback: scan ninja targets for *_test
|
||||||
|
if [ ${#TEST_NAMES[@]} -eq 0 ]; then
|
||||||
|
echo "ctest unavailable, scanning ninja targets..."
|
||||||
|
while IFS= read -r line; do
|
||||||
|
name=$(echo "$line" | sed -n 's/^\([a-zA-Z0-9_]*_test\): .*/\1/p')
|
||||||
|
[ -n "$name" ] && TEST_NAMES+=("$name")
|
||||||
|
done < <(ninja -C "$BUILD_DIR" -t targets all 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building ${#TEST_NAMES[@]} test executables..."
|
||||||
|
ninja -C "$BUILD_DIR" -j$(nproc) "${TEST_NAMES[@]}"
|
||||||
|
|
||||||
|
# ── Step 3: start emulator ─────────────────────────────────────────
|
||||||
|
- name: 'Step 3: Start Android emulator'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
AVD_NAME="zvec_test_avd"
|
||||||
export ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/26.1.10909125"
|
AVD_DIR="$HOME/.android/avd/${AVD_NAME}.avd"
|
||||||
|
AVD_INI="$HOME/.android/avd/${AVD_NAME}.ini"
|
||||||
|
|
||||||
if [ ! -d "examples/c++/build-android-examples-${{ matrix.abi }}" ]; then
|
# Find installed system image (same priority as build_android.sh)
|
||||||
cmake -S examples/c++ -B examples/c++/build-android-examples-${{ matrix.abi }} -G Ninja \
|
SYS_IMG=""
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
|
for variant in google_apis google_apis_playstore default; do
|
||||||
-DANDROID_ABI=${{ matrix.abi }} \
|
candidate="$ANDROID_HOME/system-images/android-${{ matrix.api }}/$variant/x86_64"
|
||||||
-DANDROID_PLATFORM=android-${{ matrix.api }} \
|
if [ -d "$candidate" ]; then
|
||||||
-DANDROID_STL=c++_static \
|
SYS_IMG="$candidate"
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
break
|
||||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
|
fi
|
||||||
-DHOST_BUILD_DIR="build-android-${{ matrix.abi }}" \
|
done
|
||||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
if [ -z "$SYS_IMG" ]; then
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
SYS_IMG=$(find "$ANDROID_HOME/system-images" -type d -name "x86_64" 2>/dev/null | head -1)
|
||||||
cmake --build examples/c++/build-android-examples-${{ matrix.abi }} --parallel
|
fi
|
||||||
else
|
if [ -z "$SYS_IMG" ]; then
|
||||||
echo "Using cached examples build"
|
echo "ERROR: No x86_64 system image found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "System image: $SYS_IMG"
|
||||||
|
|
||||||
|
# Extract tag from path (e.g. .../google_apis/x86_64 -> google_apis)
|
||||||
|
SYS_TAG=$(basename "$(dirname "$SYS_IMG")")
|
||||||
|
|
||||||
|
# Create AVD via INI files (faster and more reliable than avdmanager)
|
||||||
|
mkdir -p "$AVD_DIR"
|
||||||
|
|
||||||
|
cat > "$AVD_INI" << EOAVD
|
||||||
|
avd.ini.encoding=UTF-8
|
||||||
|
path=${AVD_DIR}
|
||||||
|
path.rel=avd/${AVD_NAME}.avd
|
||||||
|
target=android-${{ matrix.api }}
|
||||||
|
EOAVD
|
||||||
|
|
||||||
|
cat > "$AVD_DIR/config.ini" << EOCFG
|
||||||
|
AvdId=${AVD_NAME}
|
||||||
|
PlayStore.enabled=false
|
||||||
|
abi.type=x86_64
|
||||||
|
avd.ini.displayname=${AVD_NAME}
|
||||||
|
avd.ini.encoding=UTF-8
|
||||||
|
disk.dataPartition.size=8G
|
||||||
|
hw.accelerator.isConfigured=true
|
||||||
|
hw.cpu.arch=x86_64
|
||||||
|
hw.cpu.ncore=4
|
||||||
|
hw.lcd.density=420
|
||||||
|
hw.lcd.height=1920
|
||||||
|
hw.lcd.width=1080
|
||||||
|
hw.ramSize=4096
|
||||||
|
image.sysdir.1=${SYS_IMG}/
|
||||||
|
tag.display=${SYS_TAG}
|
||||||
|
tag.id=${SYS_TAG}
|
||||||
|
EOCFG
|
||||||
|
|
||||||
|
echo "Created AVD: $AVD_NAME"
|
||||||
|
|
||||||
|
# Launch emulator in background
|
||||||
|
$ANDROID_HOME/emulator/emulator -avd "$AVD_NAME" \
|
||||||
|
-no-window -no-audio -no-boot-anim \
|
||||||
|
-gpu swiftshader_indirect \
|
||||||
|
-netdelay none -netspeed full \
|
||||||
|
-memory 4096 \
|
||||||
|
-no-snapshot \
|
||||||
|
-wipe-data &
|
||||||
|
echo "EMULATOR_PID=$!" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
# Wait for device to be reachable
|
||||||
|
adb wait-for-device
|
||||||
|
|
||||||
|
# Poll for boot completion with timeout
|
||||||
|
echo "Waiting for boot to complete..."
|
||||||
|
TIMEOUT=300
|
||||||
|
ELAPSED=0
|
||||||
|
while true; do
|
||||||
|
BOOTED=$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r\n ' || true)
|
||||||
|
if [ "$BOOTED" = "1" ]; then
|
||||||
|
echo "Emulator ready (took ${ELAPSED}s)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ $ELAPSED -ge $TIMEOUT ]; then
|
||||||
|
echo "ERROR: Emulator failed to boot within ${TIMEOUT}s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
ELAPSED=$((ELAPSED + 3))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Device ABI: $(adb shell getprop ro.product.cpu.abi | tr -d '\r')"
|
||||||
|
echo "ABI list : $(adb shell getprop ro.product.cpu.abilist | tr -d '\r')"
|
||||||
|
|
||||||
|
# ── Step 4: run unit tests on emulator ─────────────────────────────
|
||||||
|
- name: 'Step 4: Run unit tests on emulator'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build_android_${{ matrix.abi }}
|
||||||
|
run: |
|
||||||
|
DEVICE_TEST_DIR="/data/local/tmp/zvec_tests"
|
||||||
|
DEVICE_LIB_DIR="$DEVICE_TEST_DIR/lib"
|
||||||
|
adb shell "mkdir -p $DEVICE_TEST_DIR $DEVICE_LIB_DIR"
|
||||||
|
|
||||||
|
# Push shared libraries
|
||||||
|
echo "Pushing shared libraries..."
|
||||||
|
SO_COUNT=0
|
||||||
|
while IFS= read -r so_file; do
|
||||||
|
adb push "$so_file" "$DEVICE_LIB_DIR/$(basename "$so_file")" > /dev/null 2>&1
|
||||||
|
SO_COUNT=$((SO_COUNT + 1))
|
||||||
|
done < <(find "$BUILD_DIR/lib" -name "*.so" -type f 2>/dev/null)
|
||||||
|
echo "Pushed $SO_COUNT shared libraries"
|
||||||
|
|
||||||
|
# Push helper binaries (needed by crash_recovery tests which fork+exec them)
|
||||||
|
echo "Pushing helper binaries..."
|
||||||
|
for helper_name in data_generator collection_optimizer; do
|
||||||
|
helper_path=$(find "$BUILD_DIR" -name "$helper_name" -type f -executable ! -name "*_test" 2>/dev/null | head -1)
|
||||||
|
if [ -n "$helper_path" ]; then
|
||||||
|
adb push "$helper_path" "$DEVICE_TEST_DIR/$helper_name" > /dev/null 2>&1
|
||||||
|
adb shell "chmod 755 $DEVICE_TEST_DIR/$helper_name"
|
||||||
|
echo " Pushed $helper_name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Discover test targets from ctest metadata
|
||||||
|
TEST_NAMES=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
name=$(echo "$line" | sed -n 's/.*Test[[:space:]]*#[0-9]*:[[:space:]]*//p')
|
||||||
|
[ -n "$name" ] && TEST_NAMES+=("$name")
|
||||||
|
done < <(cd "$BUILD_DIR" && ctest --show-only 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ ${#TEST_NAMES[@]} -eq 0 ]; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
name=$(echo "$line" | sed -n 's/^\([a-zA-Z0-9_]*_test\): .*/\1/p')
|
||||||
|
[ -n "$name" ] && TEST_NAMES+=("$name")
|
||||||
|
done < <(ninja -C "$BUILD_DIR" -t targets all 2>/dev/null || true)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Run on Android emulator (arm64) and verify
|
# Collect test binaries
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
TEST_BINS=()
|
||||||
with:
|
for name in "${TEST_NAMES[@]}"; do
|
||||||
api-level: ${{ matrix.api }}
|
bin_path=$(find "$BUILD_DIR" -name "$name" -type f -executable 2>/dev/null | head -1)
|
||||||
arch: ${{ matrix.abi }}
|
if [ -n "$bin_path" ]; then
|
||||||
# target: google_apis
|
TEST_BINS+=("$bin_path")
|
||||||
# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -netdelay none -netspeed full
|
else
|
||||||
# disable-animations: true
|
echo "WARNING: binary not found for '$name'"
|
||||||
script: |
|
fi
|
||||||
adb wait-for-device
|
done
|
||||||
|
|
||||||
echo "Device ABI:"
|
TOTAL=${#TEST_BINS[@]}
|
||||||
adb shell getprop ro.product.cpu.abi
|
PASSED=0
|
||||||
adb shell getprop ro.product.cpu.abilist
|
FAILED=0
|
||||||
|
FAILED_NAMES=()
|
||||||
|
IDX=0
|
||||||
|
|
||||||
echo "=== CPU ISA / Instruction Set Support ==="
|
echo "Running $TOTAL unit tests on emulator..."
|
||||||
echo "--- /proc/cpuinfo flags ---"
|
|
||||||
adb shell 'cat /proc/cpuinfo | grep -E "^(Features|flags)"'
|
|
||||||
|
|
||||||
echo "Checking binary sizes:"
|
for test_bin in "${TEST_BINS[@]}"; do
|
||||||
ls -lah examples/c++/build-android-examples-${{ matrix.abi }}/
|
IDX=$((IDX + 1))
|
||||||
|
test_name=$(basename "$test_bin")
|
||||||
|
device_path="$DEVICE_TEST_DIR/$test_name"
|
||||||
|
# Give each test its own working directory to avoid name collisions
|
||||||
|
WORK_DIR="$DEVICE_TEST_DIR/workdir_${test_name}"
|
||||||
|
|
||||||
# Push executables to device
|
echo ""
|
||||||
adb push examples/c++/build-android-examples-${{ matrix.abi }}/ailego-example /data/local/tmp/
|
echo "────────────────────────────────────────"
|
||||||
adb push examples/c++/build-android-examples-${{ matrix.abi }}/core-example /data/local/tmp/
|
echo " [$IDX/$TOTAL] $test_name"
|
||||||
adb push examples/c++/build-android-examples-${{ matrix.abi }}/db-example /data/local/tmp/
|
echo "────────────────────────────────────────"
|
||||||
|
|
||||||
adb shell chmod 755 /data/local/tmp/ailego-example
|
set +e
|
||||||
adb shell chmod 755 /data/local/tmp/core-example
|
# Create isolated working directory
|
||||||
adb shell chmod 755 /data/local/tmp/db-example
|
adb shell "mkdir -p $WORK_DIR" 2>/dev/null
|
||||||
|
|
||||||
echo "File info on device:"
|
# Copy helper binaries into working directory so crash_recovery tests
|
||||||
adb shell ls -la /data/local/tmp/ailego-example
|
# (which fork+exec data_generator / collection_optimizer) can find them
|
||||||
adb shell ls -la /data/local/tmp/core-example
|
adb shell "for h in $DEVICE_TEST_DIR/data_generator $DEVICE_TEST_DIR/collection_optimizer; do [ -f \$h ] && cp \$h $WORK_DIR/; done" 2>/dev/null
|
||||||
adb shell ls -la /data/local/tmp/db-example
|
|
||||||
|
|
||||||
echo "Running ailego example:"
|
# Push test binary
|
||||||
adb shell 'cd /data/local/tmp && ./ailego-example'
|
adb push "$test_bin" "$device_path" > /dev/null 2>&1
|
||||||
|
adb shell "chmod 755 $device_path" 2>/dev/null
|
||||||
|
|
||||||
echo "Running core example:"
|
# Run test from its own working directory with LD_LIBRARY_PATH
|
||||||
adb shell 'cd /data/local/tmp && ./core-example'
|
OUTPUT=$(adb shell "cd $WORK_DIR && LD_LIBRARY_PATH=$DEVICE_LIB_DIR $device_path 2>&1; echo EXIT_CODE=\$?" 2>&1)
|
||||||
|
|
||||||
echo "Running db example:"
|
# Extract exit code from the output
|
||||||
adb shell 'cd /data/local/tmp && ./db-example'
|
EXIT_CODE=$(echo "$OUTPUT" | grep -o 'EXIT_CODE=[0-9]*' | tail -1 | cut -d= -f2)
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Print test output (without the EXIT_CODE marker)
|
||||||
|
echo "$OUTPUT" | grep -v 'EXIT_CODE=' | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
if [ "$EXIT_CODE" = "0" ]; then
|
||||||
|
echo " >>> PASSED"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
# Detect "crash-on-exit" pattern: all gtest assertions passed but
|
||||||
|
# process crashed during static destructor teardown (common with c++_static STL)
|
||||||
|
GTEST_PASSED_LINE=$(echo "$OUTPUT" | grep '\[ PASSED \]' | tail -1 || true)
|
||||||
|
GTEST_FAILED_LINE=$(echo "$OUTPUT" | grep '\[ FAILED \]' | head -1 || true)
|
||||||
|
if [ -n "$GTEST_PASSED_LINE" ] && [ -z "$GTEST_FAILED_LINE" ] && \
|
||||||
|
{ [ "$EXIT_CODE" = "139" ] || [ "$EXIT_CODE" = "134" ] || [ "$EXIT_CODE" = "135" ]; }; then
|
||||||
|
echo " >>> PASSED (crash-on-exit ignored, exit=$EXIT_CODE)"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " >>> FAILED (exit=$EXIT_CODE)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
FAILED_NAMES+=("$test_name")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up binary and working directory to reclaim disk space
|
||||||
|
adb shell "rm -rf $device_path $WORK_DIR" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Test Summary"
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Total : $TOTAL"
|
||||||
|
echo " Passed : $PASSED"
|
||||||
|
echo " Failed : $FAILED"
|
||||||
|
if [ $FAILED -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo " Failed tests:"
|
||||||
|
for name in "${FAILED_NAMES[@]}"; do
|
||||||
|
echo " - $name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
if [ $FAILED -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All tests passed!"
|
||||||
|
|
||||||
|
# ── Step 5: build and run examples ─────────────────────────────────
|
||||||
|
- name: 'Step 5: Build and run examples'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build_android_${{ matrix.abi }}
|
||||||
|
run: |
|
||||||
|
ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||||
|
EXAMPLES_BUILD="examples/c++/build-android-examples-${{ matrix.abi }}"
|
||||||
|
|
||||||
|
cmake -S examples/c++ -B "$EXAMPLES_BUILD" -G Ninja \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
|
||||||
|
-DANDROID_ABI=${{ matrix.abi }} \
|
||||||
|
-DANDROID_PLATFORM=android-${{ matrix.api }} \
|
||||||
|
-DANDROID_STL=c++_static \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DHOST_BUILD_DIR="$BUILD_DIR" \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
cmake --build "$EXAMPLES_BUILD" --parallel
|
||||||
|
|
||||||
|
for example in ailego-example core-example db-example; do
|
||||||
|
if [ -f "$EXAMPLES_BUILD/$example" ]; then
|
||||||
|
echo "=== Running $example ==="
|
||||||
|
adb push "$EXAMPLES_BUILD/$example" "/data/local/tmp/$example" > /dev/null 2>&1
|
||||||
|
adb shell "chmod 755 /data/local/tmp/$example && cd /data/local/tmp && ./$example"
|
||||||
|
adb shell "rm -f /data/local/tmp/$example"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Cleanup ────────────────────────────────────────────────────────
|
||||||
|
- name: Stop emulator
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
adb emu kill 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
if [ -n "$EMULATOR_PID" ]; then
|
||||||
|
kill "$EMULATOR_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1274,7 +1274,18 @@ function(_find_gtest)
|
||||||
else()
|
else()
|
||||||
# Find gtest using target names
|
# Find gtest using target names
|
||||||
set(FIND_GTEST_INCS "" CACHE STRING "GTest includes")
|
set(FIND_GTEST_INCS "" CACHE STRING "GTest includes")
|
||||||
set(FIND_GTEST_LIBS "gtest;gtest_main" CACHE STRING "GTest libraries")
|
if(ANDROID)
|
||||||
|
# On Android, use a custom main that calls _exit() to skip static
|
||||||
|
# destructors and avoid glog/gflags teardown crashes.
|
||||||
|
if(NOT TARGET zvec_gtest_main)
|
||||||
|
add_library(zvec_gtest_main STATIC
|
||||||
|
${PROJECT_ROOT_DIR}/tests/android_gtest_main.cc)
|
||||||
|
target_link_libraries(zvec_gtest_main PUBLIC gtest)
|
||||||
|
endif()
|
||||||
|
set(FIND_GTEST_LIBS "gtest;zvec_gtest_main" CACHE STRING "GTest libraries")
|
||||||
|
else()
|
||||||
|
set(FIND_GTEST_LIBS "gtest;gtest_main" CACHE STRING "GTest libraries")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
@ -1321,7 +1332,18 @@ function(_find_gmock)
|
||||||
else()
|
else()
|
||||||
# Find gmock using target names
|
# Find gmock using target names
|
||||||
set(FIND_GMOCK_INCS "" CACHE STRING "GMock includes")
|
set(FIND_GMOCK_INCS "" CACHE STRING "GMock includes")
|
||||||
set(FIND_GMOCK_LIBS "gmock;gmock_main" CACHE STRING "GMock libraries")
|
if(ANDROID)
|
||||||
|
# On Android, use a custom main that calls _exit() to skip static
|
||||||
|
# destructors and avoid glog/gflags teardown crashes.
|
||||||
|
if(NOT TARGET zvec_gmock_main)
|
||||||
|
add_library(zvec_gmock_main STATIC
|
||||||
|
${PROJECT_ROOT_DIR}/tests/android_gmock_main.cc)
|
||||||
|
target_link_libraries(zvec_gmock_main PUBLIC gmock gtest)
|
||||||
|
endif()
|
||||||
|
set(FIND_GMOCK_LIBS "gmock;zvec_gmock_main" CACHE STRING "GMock libraries")
|
||||||
|
else()
|
||||||
|
set(FIND_GMOCK_LIBS "gmock;gmock_main" CACHE STRING "GMock libraries")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,395 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
# build_android.sh — cross-compile zvec for Android arm64-v8a,
|
||||||
|
# then run all C/C++ unit tests inside an emulator.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/build_android.sh [api_level] [build_type]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./scripts/build_android.sh # defaults: API 36, Release
|
||||||
|
# ./scripts/build_android.sh 36 Debug
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
CURRENT_DIR=$(pwd)
|
CURRENT_DIR=$(pwd)
|
||||||
|
ABI="arm64-v8a"
|
||||||
|
API_LEVEL=${1:-35}
|
||||||
|
BUILD_TYPE=${2:-"Release"}
|
||||||
|
CORE_COUNT=$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)
|
||||||
|
|
||||||
ABI=${1:-"arm64-v8a"}
|
# ── Android SDK paths (set later, after host protoc build) ───────────
|
||||||
API_LEVEL=${2:-21}
|
ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}
|
||||||
BUILD_TYPE=${3:-"Release"}
|
ANDROID_NDK_HOME=${ANDROID_NDK_HOME:-$(ls -d "$ANDROID_SDK_ROOT/ndk/"* 2>/dev/null | sort -V | tail -1)}
|
||||||
|
|
||||||
# step1: use host env to compile protoc
|
echo "============================================================"
|
||||||
echo "step1: building protoc for host..."
|
echo " Android Cross Build & Test"
|
||||||
|
echo "============================================================"
|
||||||
|
echo " ABI : $ABI"
|
||||||
|
echo " API Level : $API_LEVEL"
|
||||||
|
echo " Build Type : $BUILD_TYPE"
|
||||||
|
echo " NDK : $ANDROID_NDK_HOME"
|
||||||
|
echo " CPU cores : $CORE_COUNT"
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
if [ ! -d "$ANDROID_NDK_HOME" ]; then
|
||||||
|
echo "ERROR: ANDROID_NDK_HOME not found at $ANDROID_NDK_HOME"
|
||||||
|
echo "Please install the NDK via Android Studio or sdkmanager."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 1: build host protoc (using HOST compiler, NOT NDK) ─────────
|
||||||
|
echo ""
|
||||||
|
echo ">>> Step 1: Building protoc for host..."
|
||||||
HOST_BUILD_DIR="build_host"
|
HOST_BUILD_DIR="build_host"
|
||||||
mkdir -p $HOST_BUILD_DIR
|
|
||||||
cd $HOST_BUILD_DIR
|
|
||||||
|
|
||||||
cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ..
|
git submodule foreach --recursive 'git stash --include-untracked' 2>/dev/null || true
|
||||||
make -j protoc
|
|
||||||
|
if [ ! -f "$CURRENT_DIR/$HOST_BUILD_DIR/bin/protoc" ]; then
|
||||||
|
# Explicitly avoid NDK toolchain for host build
|
||||||
|
cmake -S . -B "$HOST_BUILD_DIR" \
|
||||||
|
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="" \
|
||||||
|
-G Ninja
|
||||||
|
cmake --build "$HOST_BUILD_DIR" --target protoc -j"$CORE_COUNT"
|
||||||
|
else
|
||||||
|
echo " (cached — skipping)"
|
||||||
|
fi
|
||||||
PROTOC_EXECUTABLE=$CURRENT_DIR/$HOST_BUILD_DIR/bin/protoc
|
PROTOC_EXECUTABLE=$CURRENT_DIR/$HOST_BUILD_DIR/bin/protoc
|
||||||
cd $CURRENT_DIR
|
echo ">>> Step 1: Done (protoc=$PROTOC_EXECUTABLE)"
|
||||||
|
|
||||||
echo "step1: Done!!!"
|
# ── Now export Android env vars for cross-compilation ────────────────
|
||||||
|
export ANDROID_SDK_ROOT
|
||||||
# step2: cross build zvec based on android ndk
|
|
||||||
echo "step2: building zvec for android..."
|
|
||||||
|
|
||||||
# reset thirdparty directory
|
|
||||||
git submodule foreach --recursive 'git stash --include-untracked'
|
|
||||||
|
|
||||||
export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk
|
|
||||||
export ANDROID_HOME=$ANDROID_SDK_ROOT
|
export ANDROID_HOME=$ANDROID_SDK_ROOT
|
||||||
export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/28.2.13676358
|
export ANDROID_NDK_HOME
|
||||||
export CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
|
export CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
|
||||||
|
|
||||||
export PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin
|
export PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin
|
||||||
export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools
|
export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools
|
||||||
|
export PATH=$PATH:$ANDROID_SDK_ROOT/emulator
|
||||||
export PATH=$PATH:$ANDROID_NDK_HOME
|
export PATH=$PATH:$ANDROID_NDK_HOME
|
||||||
|
|
||||||
if [ -z "$ANDROID_NDK_HOME" ]; then
|
# ── Step 2: cross-compile zvec + tests for Android ───────────────────
|
||||||
echo "error: ANDROID_NDK_HOME env not set"
|
echo ""
|
||||||
echo "please install NDK and set env variable ANDROID_NDK_HOME"
|
echo ">>> Step 2: Cross-compiling zvec for Android ($ABI, API $API_LEVEL)..."
|
||||||
exit 1
|
|
||||||
fi
|
# reset thirdparty so the cross toolchain can patch cleanly
|
||||||
|
git submodule foreach --recursive 'git stash --include-untracked' 2>/dev/null || true
|
||||||
|
|
||||||
BUILD_DIR="build_android_${ABI}"
|
BUILD_DIR="build_android_${ABI}"
|
||||||
mkdir -p $BUILD_DIR
|
|
||||||
cd $BUILD_DIR
|
|
||||||
|
|
||||||
echo "configure CMake..."
|
# Force CMake reconfigure to pick up any bazel.cmake changes
|
||||||
cmake \
|
rm -f "$BUILD_DIR/CMakeCache.txt"
|
||||||
|
|
||||||
|
cmake -S . -B "$BUILD_DIR" -G Ninja \
|
||||||
-DANDROID_NDK="$ANDROID_NDK_HOME" \
|
-DANDROID_NDK="$ANDROID_NDK_HOME" \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$CMAKE_TOOLCHAIN_FILE" \
|
||||||
-DANDROID_ABI="$ABI" \
|
-DANDROID_ABI="$ABI" \
|
||||||
-DANDROID_NATIVE_API_LEVEL="$API_LEVEL" \
|
-DANDROID_NATIVE_API_LEVEL="$API_LEVEL" \
|
||||||
-DANDROID_STL="c++_static" \
|
-DANDROID_STL="c++_static" \
|
||||||
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||||
-DBUILD_PYTHON_BINDINGS=OFF \
|
-DBUILD_PYTHON_BINDINGS=OFF \
|
||||||
-DBUILD_TOOLS=OFF \
|
-DBUILD_TOOLS=OFF \
|
||||||
-DCMAKE_INSTALL_PREFIX="./install" \
|
-DENABLE_NATIVE=OFF \
|
||||||
-DGLOBAL_CC_PROTOBUF_PROTOC=$PROTOC_EXECUTABLE \
|
-DAUTO_DETECT_ARCH=OFF \
|
||||||
../
|
-DCMAKE_INSTALL_PREFIX="$BUILD_DIR/install" \
|
||||||
|
-DGLOBAL_CC_PROTOBUF_PROTOC="$PROTOC_EXECUTABLE"
|
||||||
|
|
||||||
echo "building..."
|
echo " Building library..."
|
||||||
CORE_COUNT=$(sysctl -n hw.ncpu)
|
cmake --build "$BUILD_DIR" -j"$CORE_COUNT"
|
||||||
make -j$CORE_COUNT
|
|
||||||
|
|
||||||
echo "step2: Done!!!"
|
# Discover test target names from ctest metadata (before building)
|
||||||
|
echo " Discovering test targets from ctest..."
|
||||||
|
TEST_NAMES=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
name=$(echo "$line" | sed -n 's/.*Test[[:space:]]*#[0-9]*:[[:space:]]*//p')
|
||||||
|
if [ -n "$name" ]; then
|
||||||
|
TEST_NAMES+=("$name")
|
||||||
|
fi
|
||||||
|
done < <(cd "$BUILD_DIR" && ctest --show-only 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Fallback: find *_test targets from CMake cache
|
||||||
|
if [ ${#TEST_NAMES[@]} -eq 0 ]; then
|
||||||
|
echo " ctest listing unavailable, scanning ninja targets..."
|
||||||
|
while IFS= read -r line; do
|
||||||
|
name=$(echo "$line" | sed -n 's/^\([a-zA-Z0-9_]*_test\): .*/\1/p')
|
||||||
|
if [ -n "$name" ]; then
|
||||||
|
TEST_NAMES+=("$name")
|
||||||
|
fi
|
||||||
|
done < <(ninja -C "$BUILD_DIR" -t targets all 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Found ${#TEST_NAMES[@]} test targets."
|
||||||
|
if [ ${#TEST_NAMES[@]} -eq 0 ]; then
|
||||||
|
echo "WARNING: No test targets found. Skipping emulator step."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build all test executables at once (without running ctest)
|
||||||
|
echo " Building ${#TEST_NAMES[@]} test executables..."
|
||||||
|
ninja -C "$BUILD_DIR" -j"$CORE_COUNT" "${TEST_NAMES[@]}"
|
||||||
|
|
||||||
|
echo ">>> Step 2: Done"
|
||||||
|
|
||||||
|
# ── Step 3: collect test binaries ────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo ">>> Step 3: Collecting test binaries..."
|
||||||
|
|
||||||
|
TEST_BINS=()
|
||||||
|
for name in "${TEST_NAMES[@]}"; do
|
||||||
|
bin_path=$(find "$BUILD_DIR" -name "$name" -type f -perm +111 2>/dev/null | head -1)
|
||||||
|
if [ -n "$bin_path" ]; then
|
||||||
|
TEST_BINS+=("$bin_path")
|
||||||
|
else
|
||||||
|
echo " WARNING: binary not found for target '$name'"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo " Found ${#TEST_BINS[@]} test binaries."
|
||||||
|
if [ ${#TEST_BINS[@]} -eq 0 ]; then
|
||||||
|
echo "WARNING: No test binaries found. Skipping emulator step."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 4: start emulator ───────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo ">>> Step 4: Starting Android emulator..."
|
||||||
|
|
||||||
|
EMULATOR_BIN="$ANDROID_SDK_ROOT/emulator/emulator"
|
||||||
|
ADB_BIN="$ANDROID_SDK_ROOT/platform-tools/adb"
|
||||||
|
AVD_NAME="zvec_test_avd"
|
||||||
|
|
||||||
|
# Find a usable system image
|
||||||
|
SYS_IMG=""
|
||||||
|
for candidate in \
|
||||||
|
"$ANDROID_SDK_ROOT/system-images/android-${API_LEVEL}/default/arm64-v8a" \
|
||||||
|
"$ANDROID_SDK_ROOT/system-images/android-${API_LEVEL}/google_apis/arm64-v8a" \
|
||||||
|
"$ANDROID_SDK_ROOT/system-images/android-${API_LEVEL}/google_apis_playstore/arm64-v8a"; do
|
||||||
|
if [ -d "$candidate" ]; then
|
||||||
|
SYS_IMG="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If the exact API level isn't found, try any available arm64-v8a image
|
||||||
|
if [ -z "$SYS_IMG" ]; then
|
||||||
|
echo " No system image for API $API_LEVEL, searching for any arm64-v8a image..."
|
||||||
|
SYS_IMG=$(find "$ANDROID_SDK_ROOT/system-images" -type d -name "arm64-v8a" 2>/dev/null | head -1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$SYS_IMG" ]; then
|
||||||
|
echo "ERROR: No arm64-v8a system image found."
|
||||||
|
echo "Install one via: sdkmanager 'system-images;android-${API_LEVEL};default;arm64-v8a'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Using system image: $SYS_IMG"
|
||||||
|
|
||||||
|
# Create AVD without avdmanager (write INI files directly)
|
||||||
|
AVD_DIR="$HOME/.android/avd/${AVD_NAME}.avd"
|
||||||
|
AVD_INI="$HOME/.android/avd/${AVD_NAME}.ini"
|
||||||
|
|
||||||
|
cleanup_emulator() {
|
||||||
|
echo ""
|
||||||
|
echo ">>> Cleaning up emulator..."
|
||||||
|
$ADB_BIN emu kill 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
# Kill any lingering emulator processes for our AVD
|
||||||
|
pkill -f "emulator.*${AVD_NAME}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup_emulator EXIT
|
||||||
|
|
||||||
|
mkdir -p "$AVD_DIR"
|
||||||
|
|
||||||
|
# Write the top-level .ini
|
||||||
|
cat > "$AVD_INI" << EOF
|
||||||
|
avd.ini.encoding=UTF-8
|
||||||
|
path=${AVD_DIR}
|
||||||
|
path.rel=avd/${AVD_NAME}.avd
|
||||||
|
target=android-${API_LEVEL}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Write the AVD config
|
||||||
|
cat > "$AVD_DIR/config.ini" << EOF
|
||||||
|
AvdId=${AVD_NAME}
|
||||||
|
PlayStore.enabled=false
|
||||||
|
abi.type=arm64-v8a
|
||||||
|
avd.ini.displayname=${AVD_NAME}
|
||||||
|
avd.ini.encoding=UTF-8
|
||||||
|
disk.dataPartition.size=8G
|
||||||
|
hw.accelerator.isConfigured=true
|
||||||
|
hw.cpu.arch=arm64
|
||||||
|
hw.cpu.ncore=4
|
||||||
|
hw.lcd.density=420
|
||||||
|
hw.lcd.height=1920
|
||||||
|
hw.lcd.width=1080
|
||||||
|
hw.ramSize=4096
|
||||||
|
image.sysdir.1=${SYS_IMG}/
|
||||||
|
tag.display=Default
|
||||||
|
tag.id=default
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo " Created AVD: $AVD_NAME"
|
||||||
|
|
||||||
|
# Kill any existing emulator
|
||||||
|
$ADB_BIN emu kill 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Start emulator
|
||||||
|
echo " Launching emulator..."
|
||||||
|
$EMULATOR_BIN -avd "$AVD_NAME" \
|
||||||
|
-no-window -no-audio -no-boot-anim \
|
||||||
|
-gpu swiftshader_indirect \
|
||||||
|
-netdelay none -netspeed full \
|
||||||
|
-memory 4096 \
|
||||||
|
-no-snapshot \
|
||||||
|
-wipe-data \
|
||||||
|
2>&1 | sed 's/^/ [emulator] /' &
|
||||||
|
EMULATOR_PID=$!
|
||||||
|
|
||||||
|
# Wait for the device to become reachable
|
||||||
|
echo " Waiting for device..."
|
||||||
|
$ADB_BIN wait-for-device
|
||||||
|
|
||||||
|
# Wait for boot_completed
|
||||||
|
echo " Waiting for boot to complete..."
|
||||||
|
BOOT_TIMEOUT=300
|
||||||
|
BOOT_ELAPSED=0
|
||||||
|
while true; do
|
||||||
|
BOOTED=$($ADB_BIN shell getprop sys.boot_completed 2>/dev/null | tr -d '\r\n ' || true)
|
||||||
|
if [ "$BOOTED" = "1" ]; then
|
||||||
|
echo " Emulator is ready! (took ${BOOT_ELAPSED}s)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ $BOOT_ELAPSED -ge $BOOT_TIMEOUT ]; then
|
||||||
|
echo "ERROR: Emulator failed to boot within ${BOOT_TIMEOUT}s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
BOOT_ELAPSED=$((BOOT_ELAPSED + 3))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Print device info
|
||||||
|
echo ""
|
||||||
|
echo " Device ABI : $($ADB_BIN shell getprop ro.product.cpu.abi | tr -d '\r')"
|
||||||
|
echo " ABI list : $($ADB_BIN shell getprop ro.product.cpu.abilist | tr -d '\r')"
|
||||||
|
echo " CPU info :"
|
||||||
|
$ADB_BIN shell 'cat /proc/cpuinfo | grep -E "^(Features|flags|processor)"' 2>/dev/null | head -4 | sed 's/^/ /'
|
||||||
|
|
||||||
|
# ── Step 5: run tests on emulator ────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo ">>> Step 5: Running ${#TEST_BINS[@]} unit tests on emulator..."
|
||||||
|
|
||||||
|
DEVICE_TEST_DIR="/data/local/tmp/zvec_tests"
|
||||||
|
DEVICE_LIB_DIR="$DEVICE_TEST_DIR/lib"
|
||||||
|
$ADB_BIN shell "mkdir -p $DEVICE_TEST_DIR $DEVICE_LIB_DIR" 2>/dev/null
|
||||||
|
|
||||||
|
# Push all shared libraries that tests may depend on
|
||||||
|
echo " Pushing shared libraries..."
|
||||||
|
SO_COUNT=0
|
||||||
|
while IFS= read -r so_file; do
|
||||||
|
$ADB_BIN push "$so_file" "$DEVICE_LIB_DIR/$(basename "$so_file")" > /dev/null 2>&1
|
||||||
|
SO_COUNT=$((SO_COUNT + 1))
|
||||||
|
done < <(find "$BUILD_DIR/lib" -name "*.so" -type f 2>/dev/null)
|
||||||
|
echo " Pushed $SO_COUNT shared libraries."
|
||||||
|
|
||||||
|
# Push non-test helper binaries (e.g. data_generator, collection_optimizer)
|
||||||
|
# These are needed by crash_recovery tests which fork+exec them at runtime.
|
||||||
|
echo " Pushing helper binaries..."
|
||||||
|
HELPER_COUNT=0
|
||||||
|
for helper_name in data_generator collection_optimizer; do
|
||||||
|
helper_path=$(find "$BUILD_DIR" -name "$helper_name" -type f -perm +111 ! -name "*_test" 2>/dev/null | head -1)
|
||||||
|
if [ -n "$helper_path" ]; then
|
||||||
|
$ADB_BIN push "$helper_path" "$DEVICE_TEST_DIR/$helper_name" > /dev/null 2>&1
|
||||||
|
$ADB_BIN shell "chmod 755 $DEVICE_TEST_DIR/$helper_name" 2>/dev/null
|
||||||
|
HELPER_COUNT=$((HELPER_COUNT + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " Pushed $HELPER_COUNT helper binaries."
|
||||||
|
|
||||||
|
TOTAL=${#TEST_BINS[@]}
|
||||||
|
PASSED=0
|
||||||
|
FAILED=0
|
||||||
|
FAILED_NAMES=()
|
||||||
|
IDX=0
|
||||||
|
|
||||||
|
for test_bin in "${TEST_BINS[@]}"; do
|
||||||
|
IDX=$((IDX + 1))
|
||||||
|
test_name=$(basename "$test_bin")
|
||||||
|
device_path="$DEVICE_TEST_DIR/$test_name"
|
||||||
|
# Give each test its own working directory to avoid name collisions
|
||||||
|
# (e.g. flat_builder_test binary vs flat_builder_test/ directory)
|
||||||
|
WORK_DIR="$DEVICE_TEST_DIR/workdir_${test_name}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "────────────────────────────────────────"
|
||||||
|
echo " [$IDX/$TOTAL] $test_name"
|
||||||
|
echo "────────────────────────────────────────"
|
||||||
|
|
||||||
|
set +e
|
||||||
|
# Create isolated working directory
|
||||||
|
$ADB_BIN shell "mkdir -p $WORK_DIR" 2>/dev/null
|
||||||
|
|
||||||
|
# Copy helper binaries into working directory so crash_recovery tests
|
||||||
|
# (which fork+exec data_generator / collection_optimizer) can find them.
|
||||||
|
$ADB_BIN shell "for h in $DEVICE_TEST_DIR/data_generator $DEVICE_TEST_DIR/collection_optimizer; do [ -f \$h ] && cp \$h $WORK_DIR/; done" 2>/dev/null
|
||||||
|
|
||||||
|
# Push binary
|
||||||
|
$ADB_BIN push "$test_bin" "$device_path" > /dev/null 2>&1
|
||||||
|
$ADB_BIN shell "chmod 755 $device_path" 2>/dev/null
|
||||||
|
|
||||||
|
# Run test from its own working directory with LD_LIBRARY_PATH
|
||||||
|
OUTPUT=$($ADB_BIN shell "cd $WORK_DIR && LD_LIBRARY_PATH=$DEVICE_LIB_DIR $device_path 2>&1; echo EXIT_CODE=\$?" 2>&1)
|
||||||
|
|
||||||
|
# Extract exit code from the output
|
||||||
|
EXIT_CODE=$(echo "$OUTPUT" | grep -o 'EXIT_CODE=[0-9]*' | tail -1 | cut -d= -f2)
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Print test output (without the EXIT_CODE line)
|
||||||
|
echo "$OUTPUT" | grep -v 'EXIT_CODE=' | sed 's/^/ /'
|
||||||
|
|
||||||
|
if [ "$EXIT_CODE" = "0" ]; then
|
||||||
|
echo " >>> PASSED"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
# Detect "segfault-on-exit" pattern: all gtest assertions passed but
|
||||||
|
# process crashed during teardown (common with cross-compiled gmock tests).
|
||||||
|
GTEST_PASSED_LINE=$(echo "$OUTPUT" | grep '\[ PASSED \]' | tail -1)
|
||||||
|
GTEST_FAILED_LINE=$(echo "$OUTPUT" | grep '\[ FAILED \]' | head -1)
|
||||||
|
if [ -n "$GTEST_PASSED_LINE" ] && [ -z "$GTEST_FAILED_LINE" ] && \
|
||||||
|
{ [ "$EXIT_CODE" = "139" ] || [ "$EXIT_CODE" = "134" ] || [ "$EXIT_CODE" = "135" ]; }; then
|
||||||
|
echo " >>> PASSED (with crash-on-exit, exit code: $EXIT_CODE — ignored)"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo " >>> FAILED (exit code: $EXIT_CODE)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
FAILED_NAMES+=("$test_name")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up binary and working directory to reclaim disk space
|
||||||
|
$ADB_BIN shell "rm -rf $device_path $WORK_DIR" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Summary ──────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Test Summary"
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Total : $TOTAL"
|
||||||
|
echo " Passed : $PASSED"
|
||||||
|
echo " Failed : $FAILED"
|
||||||
|
if [ $FAILED -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo " Failed tests:"
|
||||||
|
for name in "${FAILED_NAMES[@]}"; do
|
||||||
|
echo " - $name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
if [ $FAILED -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All tests passed!"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Custom gmock main for Android cross-compiled tests.
|
||||||
|
//
|
||||||
|
// On Android (NDK c++_static), static destructors of glog/gflags/etc. run
|
||||||
|
// after main() returns, often in unpredictable order. This causes segfaults
|
||||||
|
// (exit code 139) or aborts (134/135) during teardown even when all tests
|
||||||
|
// passed. Using _exit() skips the static destructor phase entirely.
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
|
GTEST_API_ int main(int argc, char **argv) {
|
||||||
|
printf("Running main() from %s\n", __FILE__);
|
||||||
|
// InitGoogleMock also initializes GoogleTest internally.
|
||||||
|
testing::InitGoogleMock(&argc, argv);
|
||||||
|
int ret = RUN_ALL_TESTS();
|
||||||
|
// Flush all output so the test runner can parse results
|
||||||
|
std::cout.flush();
|
||||||
|
std::cerr.flush();
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
// Use _exit() to skip static destructors and avoid glog/gflags teardown
|
||||||
|
// crashes that are common with NDK c++_static builds.
|
||||||
|
_exit(ret);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Custom gtest main for Android cross-compiled tests.
|
||||||
|
//
|
||||||
|
// On Android (NDK c++_static), static destructors of glog/gflags/etc. run
|
||||||
|
// after main() returns, often in unpredictable order. This causes segfaults
|
||||||
|
// (exit code 139) or aborts (134/135) during teardown even when all tests
|
||||||
|
// passed. Using _exit() skips the static destructor phase entirely.
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
GTEST_API_ int main(int argc, char **argv) {
|
||||||
|
printf("Running main() from %s\n", __FILE__);
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
int ret = RUN_ALL_TESTS();
|
||||||
|
// Flush all output so the test runner can parse results
|
||||||
|
std::cout.flush();
|
||||||
|
std::cerr.flush();
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
// Use _exit() to skip static destructors and avoid glog/gflags teardown
|
||||||
|
// crashes that are common with NDK c++_static builds.
|
||||||
|
_exit(ret);
|
||||||
|
}
|
||||||
|
|
@ -902,7 +902,7 @@ void test_collection_basic_operations(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
// Create temporary directory
|
// Create temporary directory
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_basic_operations";
|
char temp_dir[] = "./zvec_test_collection_basic_operations";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -996,7 +996,7 @@ void test_collection_basic_operations(void) {
|
||||||
void test_collection_edge_cases(void) {
|
void test_collection_edge_cases(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_edge_cases";
|
char temp_dir[] = "./zvec_test_collection_edge_cases";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -1045,7 +1045,7 @@ void test_collection_edge_cases(void) {
|
||||||
void test_collection_delete_by_filter(void) {
|
void test_collection_delete_by_filter(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_delete_by_filter";
|
char temp_dir[] = "./zvec_test_collection_delete_by_filter";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -1084,7 +1084,7 @@ void test_collection_delete_by_filter(void) {
|
||||||
void test_collection_stats(void) {
|
void test_collection_stats(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_stats";
|
char temp_dir[] = "./zvec_test_collection_stats";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -3731,7 +3731,7 @@ void test_query_params_functions(void) {
|
||||||
void test_collection_stats_functions(void) {
|
void test_collection_stats_functions(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_stats_functions";
|
char temp_dir[] = "./zvec_test_collection_stats_functions";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -3778,7 +3778,7 @@ void test_collection_stats_functions(void) {
|
||||||
void test_collection_dml_functions(void) {
|
void test_collection_dml_functions(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_dml";
|
char temp_dir[] = "./zvec_test_collection_dml";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -3904,7 +3904,7 @@ void test_collection_dml_functions(void) {
|
||||||
void test_collection_nullable_roundtrip(void) {
|
void test_collection_nullable_roundtrip(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_nullable_roundtrip";
|
char temp_dir[] = "./zvec_test_collection_nullable_roundtrip";
|
||||||
zvec_test_delete_dir(temp_dir);
|
zvec_test_delete_dir(temp_dir);
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
|
|
@ -4003,7 +4003,7 @@ void test_collection_nullable_roundtrip(void) {
|
||||||
void test_actual_vector_queries(void) {
|
void test_actual_vector_queries(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_actual_queries";
|
char temp_dir[] = "./zvec_test_actual_queries";
|
||||||
|
|
||||||
// Create schema with vector field
|
// Create schema with vector field
|
||||||
zvec_collection_schema_t *schema =
|
zvec_collection_schema_t *schema =
|
||||||
|
|
@ -4129,7 +4129,7 @@ void test_actual_vector_queries(void) {
|
||||||
void test_index_creation_and_management(void) {
|
void test_index_creation_and_management(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_index_management";
|
char temp_dir[] = "./zvec_test_index_management";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -4185,7 +4185,7 @@ void test_index_creation_and_management(void) {
|
||||||
void test_collection_ddl_operations(void) {
|
void test_collection_ddl_operations(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_collection_ddl";
|
char temp_dir[] = "./zvec_test_collection_ddl";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
zvec_collection_schema_t *schema = zvec_test_create_temp_schema();
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -4294,7 +4294,7 @@ void test_field_ddl_operations(void) {
|
||||||
void test_performance_benchmarks(void) {
|
void test_performance_benchmarks(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
char temp_dir[] = "/tmp/zvec_test_performance";
|
char temp_dir[] = "./zvec_test_performance";
|
||||||
|
|
||||||
zvec_collection_schema_t *schema = zvec_collection_schema_create("perf_test");
|
zvec_collection_schema_t *schema = zvec_collection_schema_create("perf_test");
|
||||||
TEST_ASSERT(schema != NULL);
|
TEST_ASSERT(schema != NULL);
|
||||||
|
|
@ -4547,7 +4547,7 @@ void test_index_params_creation_functions(void) {
|
||||||
void test_collection_advanced_index_functions(void) {
|
void test_collection_advanced_index_functions(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
const char *temp_dir = "/tmp/zvec_test_advanced_index";
|
const char *temp_dir = "./zvec_test_advanced_index";
|
||||||
zvec_test_delete_dir(temp_dir);
|
zvec_test_delete_dir(temp_dir);
|
||||||
|
|
||||||
// Create schema
|
// Create schema
|
||||||
|
|
@ -4635,7 +4635,7 @@ void test_collection_advanced_index_functions(void) {
|
||||||
void test_collection_query_functions(void) {
|
void test_collection_query_functions(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
const char *temp_dir = "/tmp/zvec_test_query_funcs";
|
const char *temp_dir = "./zvec_test_query_funcs";
|
||||||
zvec_test_delete_dir(temp_dir);
|
zvec_test_delete_dir(temp_dir);
|
||||||
|
|
||||||
// Create schema and collection
|
// Create schema and collection
|
||||||
|
|
@ -4906,7 +4906,7 @@ void test_array_memory_functions(void) {
|
||||||
void test_collection_open_close(void) {
|
void test_collection_open_close(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
const char *temp_dir = "/tmp/zvec_test_open_close";
|
const char *temp_dir = "./zvec_test_open_close";
|
||||||
zvec_test_delete_dir(temp_dir);
|
zvec_test_delete_dir(temp_dir);
|
||||||
|
|
||||||
// First create a collection
|
// First create a collection
|
||||||
|
|
@ -5074,7 +5074,7 @@ void test_collection_options_getters(void) {
|
||||||
void test_collection_stats_index_info(void) {
|
void test_collection_stats_index_info(void) {
|
||||||
TEST_START();
|
TEST_START();
|
||||||
|
|
||||||
const char *temp_dir = "/tmp/zvec_test_stats_index";
|
const char *temp_dir = "./zvec_test_stats_index";
|
||||||
zvec_test_delete_dir(temp_dir);
|
zvec_test_delete_dir(temp_dir);
|
||||||
|
|
||||||
zvec_collection_schema_t *schema =
|
zvec_collection_schema_t *schema =
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,9 @@ TEST_F(FlatStreamerTest, TestLinearSearch) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(FlatStreamerTest, TestLinearSearchWithLRU) {
|
TEST_F(FlatStreamerTest, TestLinearSearchWithLRU) {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
GTEST_SKIP() << "Skipped on Android: requires ~6GB memory/disk (emulator limit)";
|
||||||
|
#endif
|
||||||
constexpr size_t static dim = 1600;
|
constexpr size_t static dim = 1600;
|
||||||
IndexStreamer::Pointer write_streamer =
|
IndexStreamer::Pointer write_streamer =
|
||||||
IndexFactory::CreateStreamer("FlatStreamer");
|
IndexFactory::CreateStreamer("FlatStreamer");
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,8 @@ TEST_F(CollectionTest, Feature_CreateAndOpen_PathValidate) {
|
||||||
"v1.2_alpha-beta",
|
"v1.2_alpha-beta",
|
||||||
".hidden",
|
".hidden",
|
||||||
"file.txt",
|
"file.txt",
|
||||||
"/tmp/absolute/path",
|
"abs_test/nested/path",
|
||||||
"/tmp/a/b/c",
|
"nested/a/b/c",
|
||||||
"_",
|
"_",
|
||||||
"-",
|
"-",
|
||||||
"./tmp"};
|
"./tmp"};
|
||||||
|
|
@ -2123,6 +2123,9 @@ TEST_F(CollectionTest, Feature_CreateIndex_Vector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CollectionTest, Feature_CreateIndex_Scalar) {
|
TEST_F(CollectionTest, Feature_CreateIndex_Scalar) {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
GTEST_SKIP() << "Skipped on Android: emulator filesystem lacks hardlink support (needed by RocksDB checkpoint)";
|
||||||
|
#endif
|
||||||
auto func = [&](std::string field_name, bool enable_optimize,
|
auto func = [&](std::string field_name, bool enable_optimize,
|
||||||
IndexParams::Ptr scalar_index_params = nullptr) {
|
IndexParams::Ptr scalar_index_params = nullptr) {
|
||||||
FileHelper::RemoveDirectory(col_path);
|
FileHelper::RemoveDirectory(col_path);
|
||||||
|
|
@ -2397,6 +2400,9 @@ TEST_F(CollectionTest, Feature_DropIndex_Vector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CollectionTest, Feature_DropIndex_Scalar) {
|
TEST_F(CollectionTest, Feature_DropIndex_Scalar) {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
GTEST_SKIP() << "Skipped on Android: emulator filesystem lacks hardlink support (needed by RocksDB checkpoint)";
|
||||||
|
#endif
|
||||||
auto func = [&](std::string field_name, bool enable_optimize) {
|
auto func = [&](std::string field_name, bool enable_optimize) {
|
||||||
FileHelper::RemoveDirectory(col_path);
|
FileHelper::RemoveDirectory(col_path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ int main(int argc, char **argv) {
|
||||||
// Parse arguments
|
// Parse arguments
|
||||||
if (!ParseArgs(argc, argv, config)) {
|
if (!ParseArgs(argc, argv, config)) {
|
||||||
PrintUsage(argv[0]);
|
PrintUsage(argv[0]);
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,15 +82,21 @@ int main(int argc, char **argv) {
|
||||||
std::cout << " Path: " << config.path << std::endl;
|
std::cout << " Path: " << config.path << std::endl;
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
auto result =
|
// Scope 'result' so its shared_ptr is released before we call _exit().
|
||||||
zvec::Collection::Open(config.path, zvec::CollectionOptions{false, true});
|
zvec::Collection::Ptr collection;
|
||||||
if (!result) {
|
{
|
||||||
LOG_ERROR("Failed to open collection[%s]: %s", config.path.c_str(),
|
auto result =
|
||||||
result.error().c_str());
|
zvec::Collection::Open(config.path, zvec::CollectionOptions{false, true});
|
||||||
return -1;
|
if (!result) {
|
||||||
|
LOG_ERROR("Failed to open collection[%s]: %s", config.path.c_str(),
|
||||||
|
result.error().c_str());
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
collection = result.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto collection = result.value();
|
|
||||||
std::cout << "Collection[" << config.path.c_str() << "] opened successfully"
|
std::cout << "Collection[" << config.path.c_str() << "] opened successfully"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
|
|
@ -101,9 +110,30 @@ int main(int argc, char **argv) {
|
||||||
// Print final stats
|
// Print final stats
|
||||||
std::cout << "Final stats: " << collection->Stats()->to_string_formatted()
|
std::cout << "Final stats: " << collection->Stats()->to_string_formatted()
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
// On Android with c++_static STL, static destructors of glog/gflags
|
||||||
|
// crash during process teardown. Use _exit() to skip them.
|
||||||
|
collection->Flush();
|
||||||
|
collection.reset();
|
||||||
|
sync();
|
||||||
|
std::cout.flush();
|
||||||
|
std::cerr.flush();
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
_exit(0);
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "Optimize failed: " << s.message() << std::endl;
|
std::cout << "Optimize failed: " << s.message() << std::endl;
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
collection.reset();
|
||||||
|
sync();
|
||||||
|
std::cout.flush();
|
||||||
|
std::cerr.flush();
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#include <unistd.h> // _exit()
|
||||||
|
#endif
|
||||||
#include <zvec/ailego/internal/platform.h>
|
#include <zvec/ailego/internal/platform.h>
|
||||||
#include <zvec/db/collection.h>
|
#include <zvec/db/collection.h>
|
||||||
#include "zvec/ailego/logger/logger.h"
|
#include "zvec/ailego/logger/logger.h"
|
||||||
|
|
@ -114,6 +117,9 @@ int main(int argc, char **argv) {
|
||||||
// Parse arguments
|
// Parse arguments
|
||||||
if (!ParseArgs(argc, argv, config)) {
|
if (!ParseArgs(argc, argv, config)) {
|
||||||
PrintUsage(argv[0]);
|
PrintUsage(argv[0]);
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,15 +142,25 @@ int main(int argc, char **argv) {
|
||||||
std::cout << " BatchDelay: " << kBatchDelayMs << "ms" << std::endl;
|
std::cout << " BatchDelay: " << kBatchDelayMs << "ms" << std::endl;
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
auto result = zvec::Collection::Open(
|
// Scope 'result' so its shared_ptr is released before we call _exit().
|
||||||
config.path, zvec::CollectionOptions{false, true, 4 * 1024 * 1024});
|
// Without scoping, result.value() returns a reference and the copy means
|
||||||
if (!result) {
|
// result still owns a second shared_ptr; collection.reset() would only
|
||||||
LOG_ERROR("Failed to open collection[%s]: %s", config.path.c_str(),
|
// drop the refcount to 1, and _exit() would skip the Collection destructor,
|
||||||
result.error().c_str());
|
// leaving the WAL dirty.
|
||||||
return -1;
|
zvec::Collection::Ptr collection;
|
||||||
|
{
|
||||||
|
auto result = zvec::Collection::Open(
|
||||||
|
config.path, zvec::CollectionOptions{false, true, 4 * 1024 * 1024});
|
||||||
|
if (!result) {
|
||||||
|
LOG_ERROR("Failed to open collection[%s]: %s", config.path.c_str(),
|
||||||
|
result.error().c_str());
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
collection = result.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto collection = result.value();
|
|
||||||
LOG_INFO("Collection[%s] opened successfully", config.path.c_str());
|
LOG_INFO("Collection[%s] opened successfully", config.path.c_str());
|
||||||
|
|
||||||
// Process documents in batches
|
// Process documents in batches
|
||||||
|
|
@ -181,12 +197,22 @@ int main(int argc, char **argv) {
|
||||||
if (!results) {
|
if (!results) {
|
||||||
LOG_ERROR("Failed to perform operation[%s], reason: %s",
|
LOG_ERROR("Failed to perform operation[%s], reason: %s",
|
||||||
config.operation.c_str(), results.error().message().c_str());
|
config.operation.c_str(), results.error().message().c_str());
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
collection.reset();
|
||||||
|
sync();
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
for (auto &s : results.value()) {
|
for (auto &s : results.value()) {
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
LOG_ERROR("Failed to perform operation[%s], reason: %s",
|
LOG_ERROR("Failed to perform operation[%s], reason: %s",
|
||||||
config.operation.c_str(), s.message().c_str());
|
config.operation.c_str(), s.message().c_str());
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
collection.reset();
|
||||||
|
sync();
|
||||||
|
_exit(1);
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,5 +239,19 @@ int main(int argc, char **argv) {
|
||||||
std::cout << "Success! Processed " << processed << " documents in "
|
std::cout << "Success! Processed " << processed << " documents in "
|
||||||
<< batch_num << " batches." << std::endl;
|
<< batch_num << " batches." << std::endl;
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
// On Android with c++_static STL, static destructors of glog/gflags
|
||||||
|
// crash during process teardown. Use _exit() to skip them.
|
||||||
|
// Flush + close the collection to persist all buffered data (WAL, memtable),
|
||||||
|
// then sync() to ensure kernel buffers are written to disk before _exit().
|
||||||
|
collection->Flush();
|
||||||
|
collection.reset();
|
||||||
|
sync();
|
||||||
|
std::cout.flush();
|
||||||
|
std::cerr.flush();
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
_exit(0);
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,9 @@ TEST_F(InvertedIndexTest, SEALED) {
|
||||||
|
|
||||||
|
|
||||||
TEST_F(InvertedIndexTest, SNAPSHOT) {
|
TEST_F(InvertedIndexTest, SNAPSHOT) {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
GTEST_SKIP() << "Skipped on Android: emulator filesystem lacks hardlink support (needed by RocksDB checkpoint)";
|
||||||
|
#endif
|
||||||
ASSERT_TRUE(indexer_);
|
ASSERT_TRUE(indexer_);
|
||||||
|
|
||||||
ASSERT_TRUE(indexer_->create_snapshot(working_dir + "snapshot").ok());
|
ASSERT_TRUE(indexer_->create_snapshot(working_dir + "snapshot").ok());
|
||||||
|
|
|
||||||
|
|
@ -697,6 +697,9 @@ TEST_F(InvertedIndexTest, SEALED) {
|
||||||
|
|
||||||
|
|
||||||
TEST_F(InvertedIndexTest, CREATE_SNAPSHOT) {
|
TEST_F(InvertedIndexTest, CREATE_SNAPSHOT) {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
GTEST_SKIP() << "Skipped on Android: emulator filesystem lacks hardlink support (needed by RocksDB checkpoint)";
|
||||||
|
#endif
|
||||||
ASSERT_TRUE(indexer_);
|
ASSERT_TRUE(indexer_);
|
||||||
|
|
||||||
std::string snapshot_dir = working_dir + "snapshot";
|
std::string snapshot_dir = working_dir + "snapshot";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue