Add UBSan build support and UBSan CI workflow (#2298) #6703
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Licensed to the Apache Software Foundation (ASF) under one | |
# or more contributor license agreements. See the NOTICE file | |
# distributed with this work for additional information | |
# regarding copyright ownership. The ASF licenses this file | |
# to you under the Apache License, Version 2.0 (the | |
# "License"); you may not use this file except in compliance | |
# with the License. You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, | |
# software distributed under the License is distributed on an | |
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
# KIND, either express or implied. See the License for the | |
# specific language governing permissions and limitations | |
# under the License. | |
name: CI | |
on: [push, pull_request] | |
# Concurrency strategy: | |
# github.workflow: distinguish this workflow from others | |
# github.event_name: distinguish `push` event from `pull_request` event | |
# github.event.number: set to the number of the pull request if `pull_request` event | |
# github.run_id: otherwise, it's a `push` event, only cancel if we rerun the workflow | |
# | |
# Reference: | |
# https://docs.github.com/en/actions/using-jobs/using-concurrency | |
# https://docs.github.com/en/actions/learn-github-actions/contexts#github-context | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.number || github.run_id }} | |
cancel-in-progress: true | |
jobs: | |
precondition: | |
name: Precondition | |
runs-on: ubuntu-22.04 | |
outputs: | |
docs_only: ${{ steps.result.outputs.docs_only }} | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: dorny/paths-filter@v3.0.0 | |
id: changes | |
with: | |
filters: .github/config/changes.yml | |
list-files: csv | |
- name: Calculate changes | |
id: result | |
run: | | |
echo "docs_only=${{ fromJSON(steps.changes.outputs.all_count) == fromJSON(steps.changes.outputs.docs_count) && fromJSON(steps.changes.outputs.docs_count) > 0 }}" >> $GITHUB_OUTPUT | |
check-typos: | |
name: Check typos | |
runs-on: ubuntu-22.04 | |
env: | |
FORCE_COLOR: 1 | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Install typos | |
run: curl -LsSf https://github.com/crate-ci/typos/releases/download/v1.18.2/typos-v1.18.2-x86_64-unknown-linux-musl.tar.gz | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin | |
- name: Run typos check | |
run: typos --config .github/config/typos.toml | |
check-and-lint: | |
name: Lint and check code | |
needs: [precondition] | |
if: ${{ needs.precondition.outputs.docs_only != 'true' }} | |
runs-on: ubuntu-22.04 | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: actions/setup-go@v5 | |
with: | |
go-version-file: 'tests/gocase/go.mod' | |
cache: false | |
- name: Prepare Dependencies | |
run: | | |
sudo apt update | |
sudo apt install -y clang-format-14 clang-tidy-14 | |
- uses: apache/skywalking-eyes/header@v0.6.0 | |
with: | |
config: .github/config/licenserc.yml | |
- name: Check with clang-format | |
id: check-format | |
run: ./x.py check format --clang-format-path clang-format-14 | |
- name: Check with clang-tidy | |
run: | | |
./x.py build --skip-build | |
./x.py check tidy -j $(nproc) --clang-tidy-path clang-tidy-14 --run-clang-tidy-path run-clang-tidy-14 | |
- name: Lint with golangci-lint | |
run: ./x.py check golangci-lint | |
- name: Prepare format patch | |
if: always() && steps.check-format.outcome != 'success' | |
run: | | |
./x.py format --clang-format-path clang-format-14 | |
git diff -p > clang-format.patch | |
cat clang-format.patch | |
- name: Upload format patch | |
uses: actions/upload-artifact@v4 | |
if: always() && steps.check-format.outcome != 'success' | |
with: | |
path: clang-format.patch | |
build-and-test: | |
name: Build and test | |
needs: [precondition, check-and-lint, check-typos] | |
if: ${{ needs.precondition.outputs.docs_only != 'true' }} | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
- name: Darwin Clang | |
os: macos-11 | |
compiler: auto | |
- name: Darwin Clang arm64 | |
os: macos-14 | |
compiler: auto | |
- name: Darwin Clang without Jemalloc | |
os: macos-11 | |
compiler: auto | |
without_jemalloc: -DDISABLE_JEMALLOC=ON | |
- name: Darwin Clang with OpenSSL | |
os: macos-11 | |
compiler: auto | |
with_openssl: -DENABLE_OPENSSL=ON | |
- name: Darwin Clang without luaJIT | |
os: macos-11 | |
compiler: auto | |
without_luajit: -DENABLE_LUAJIT=OFF | |
- name: Ubuntu GCC | |
os: ubuntu-20.04 | |
compiler: gcc | |
- name: SonarCloud with Coverage | |
os: ubuntu-22.04 | |
compiler: gcc | |
sonarcloud: -DCMAKE_CXX_FLAGS=--coverage | |
- name: Ubuntu Clang | |
os: ubuntu-20.04 | |
compiler: clang | |
- name: Ubuntu 22 GCC | |
os: ubuntu-22.04 | |
compiler: gcc | |
- name: Ubuntu 22 Clang | |
os: ubuntu-22.04 | |
compiler: clang | |
- name: Ubuntu GCC ASan | |
os: ubuntu-20.04 | |
without_jemalloc: -DDISABLE_JEMALLOC=ON | |
with_sanitizer: -DENABLE_ASAN=ON | |
compiler: gcc | |
- name: Ubuntu Clang ASan | |
os: ubuntu-20.04 | |
with_sanitizer: -DENABLE_ASAN=ON | |
without_jemalloc: -DDISABLE_JEMALLOC=ON | |
compiler: clang | |
- name: Ubuntu GCC TSan | |
os: ubuntu-22.04 | |
without_jemalloc: -DDISABLE_JEMALLOC=ON | |
with_sanitizer: -DENABLE_TSAN=ON | |
compiler: gcc | |
ignore_when_tsan: -tags="ignore_when_tsan" | |
- name: Ubuntu Clang TSan | |
os: ubuntu-20.04 | |
with_sanitizer: -DENABLE_TSAN=ON | |
without_jemalloc: -DDISABLE_JEMALLOC=ON | |
compiler: clang | |
ignore_when_tsan: -tags="ignore_when_tsan" | |
- name: Ubuntu Clang UBSAN | |
os: ubuntu-20.04 | |
with_sanitizer: -DENABLE_UBSAN=ON | |
without_jemalloc: -DDISABLE_JEMALLOC=ON | |
compiler: clang | |
- name: Ubuntu GCC Ninja | |
os: ubuntu-20.04 | |
with_ninja: --ninja | |
compiler: gcc | |
- name: Ubuntu GCC with OpenSSL | |
os: ubuntu-20.04 | |
compiler: gcc | |
with_openssl: -DENABLE_OPENSSL=ON | |
- name: Ubuntu Clang with OpenSSL | |
os: ubuntu-22.04 | |
compiler: clang | |
with_openssl: -DENABLE_OPENSSL=ON | |
- name: Ubuntu GCC without luaJIT | |
os: ubuntu-20.04 | |
without_luajit: -DENABLE_LUAJIT=OFF | |
compiler: gcc | |
- name: Ubuntu Clang without luaJIT | |
os: ubuntu-20.04 | |
without_luajit: -DENABLE_LUAJIT=OFF | |
compiler: clang | |
- name: Ubuntu GCC with new encoding | |
os: ubuntu-20.04 | |
compiler: gcc | |
new_encoding: -DENABLE_NEW_ENCODING=TRUE | |
- name: Ubuntu Clang with new encoding | |
os: ubuntu-22.04 | |
compiler: clang | |
new_encoding: -DENABLE_NEW_ENCODING=TRUE | |
- name: Ubuntu GCC with speedb enabled | |
os: ubuntu-20.04 | |
compiler: gcc | |
with_speedb: -DENABLE_SPEEDB=ON | |
runs-on: ${{ matrix.os }} | |
env: | |
SONARCLOUD_OUTPUT_DIR: sonarcloud-data | |
steps: | |
- name: Setup macOS | |
if: ${{ startsWith(matrix.os, 'macos') }} | |
run: | | |
brew install cmake gcc autoconf automake libtool openssl | |
echo "NPROC=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV | |
echo "CMAKE_EXTRA_DEFS=-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl" >> $GITHUB_ENV | |
- name: Setup Linux | |
if: ${{ startsWith(matrix.os, 'ubuntu') }} | |
run: | | |
sudo apt update | |
sudo apt install -y ninja-build | |
echo "NPROC=$(nproc)" >> $GITHUB_ENV | |
- name: Cache redis | |
id: cache-redis | |
uses: actions/cache@v4 | |
with: | |
path: | | |
~/local/bin/redis-cli | |
key: ${{ runner.os }}-${{ runner.arch }}-redis-cli | |
- name: Cache redis server | |
id: cache-redis-server | |
uses: actions/cache@v4 | |
with: | |
path: | | |
~/local/bin/redis-server | |
key: ${{ runner.os }}-${{ runner.arch }}-redis-server | |
- name: Install redis | |
if: ${{ steps.cache-redis.outputs.cache-hit != 'true' || steps.cache-redis-server.outputs.cache-hit != 'true' }} | |
run: | | |
curl -O https://download.redis.io/releases/redis-6.2.14.tar.gz | |
tar -xzvf redis-6.2.14.tar.gz | |
mkdir -p $HOME/local/bin | |
pushd redis-6.2.14 && BUILD_TLS=yes make -j$NPROC redis-cli && mv src/redis-cli $HOME/local/bin/ && popd | |
pushd redis-6.2.14 && BUILD_TLS=yes make -j$NPROC redis-server && mv src/redis-server $HOME/local/bin/ && popd | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- uses: actions/setup-python@v5 | |
with: | |
python-version: 3.x | |
- uses: actions/setup-go@v5 | |
with: | |
go-version-file: 'tests/gocase/go.mod' | |
cache: false | |
- name: Install gcovr 5.0 | |
run: pip install gcovr==5.0 # 5.1 is not supported | |
if: ${{ matrix.sonarcloud }} | |
- name: Install sonar-scanner and build-wrapper | |
uses: SonarSource/sonarcloud-github-c-cpp@v2 | |
if: ${{ matrix.sonarcloud }} | |
- name: Build Kvrocks | |
if: ${{ !matrix.sonarcloud }} | |
run: | | |
./x.py build -j$NPROC --unittest --compiler ${{ matrix.compiler }} ${{ matrix.without_jemalloc }} \ | |
${{ matrix.without_luajit }} ${{ matrix.with_ninja }} ${{ matrix.with_sanitizer }} ${{ matrix.with_openssl }} \ | |
${{ matrix.new_encoding }} ${{ matrix.with_speedb }} ${{ env.CMAKE_EXTRA_DEFS }} | |
- name: Build Kvrocks (SonarCloud) | |
if: ${{ matrix.sonarcloud }} | |
run: | | |
build-wrapper-linux-x86-64 --out-dir ${{ env.SONARCLOUD_OUTPUT_DIR }} ./x.py build -j$NPROC --unittest --compiler ${{ matrix.compiler }} ${{ matrix.sonarcloud }} | |
- name: Setup Coredump | |
if: ${{ startsWith(matrix.os, 'ubuntu') }} | |
run: | | |
echo "$(pwd)/coredumps/corefile-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern | |
mkdir coredumps | |
- name: Run Unit Test | |
run: | | |
ulimit -c unlimited | |
export LSAN_OPTIONS="suppressions=$(realpath ./tests/lsan-suppressions)" | |
export TSAN_OPTIONS="suppressions=$(realpath ./tests/tsan-suppressions)" | |
./x.py test cpp | |
- name: Run Go Integration Cases | |
run: | | |
ulimit -c unlimited | |
export LSAN_OPTIONS="suppressions=$(realpath ./tests/lsan-suppressions)" | |
export TSAN_OPTIONS="suppressions=$(realpath ./tests/tsan-suppressions)" | |
export PATH=$PATH:$HOME/local/bin/ | |
GOCASE_RUN_ARGS="" | |
if [[ -n "${{ matrix.with_openssl }}" ]] && [[ "${{ matrix.os }}" == ubuntu* ]]; then | |
git clone https://github.com/jsha/minica | |
cd minica && git checkout 96a5c93723cf3d34b50b3e723a9f05cd3765bc67 && go build && cd .. | |
./minica/minica --domains localhost | |
cp localhost/cert.pem tests/gocase/tls/cert/server.crt | |
cp localhost/key.pem tests/gocase/tls/cert/server.key | |
cp minica.pem tests/gocase/tls/cert/ca.crt | |
GOCASE_RUN_ARGS="-tlsEnable" | |
fi | |
./x.py test go build $GOCASE_RUN_ARGS ${{ matrix.ignore_when_tsan}} | |
- name: Install redis-py | |
run: pip3 install redis==4.3.6 | |
- name: Run kvrocks2redis Test | |
# Currently, when enabling Tsan/Asan or running in macOS 11/14, the value mismatch in destination redis server. | |
# See https://github.com/apache/kvrocks/issues/2195. | |
if: ${{ !contains(matrix.name, 'Tsan') && !contains(matrix.name, 'Asan') && !startsWith(matrix.os, 'macos') }} | |
run: | | |
ulimit -c unlimited | |
export LSAN_OPTIONS="suppressions=$(realpath ./tests/lsan-suppressions)" | |
export TSAN_OPTIONS="suppressions=$(realpath ./tests/tsan-suppressions)" | |
$HOME/local/bin/redis-server --daemonize yes | |
mkdir -p kvrocks2redis-ci-data | |
./build/kvrocks --dir `pwd`/kvrocks2redis-ci-data --pidfile `pwd`/kvrocks.pid --daemonize yes | |
sleep 10s | |
echo -en "data-dir `pwd`/kvrocks2redis-ci-data\ndaemonize yes\noutput-dir ./\nnamespace.__namespace 127.0.0.1 6379\n" >> ./kvrocks2redis-ci.conf | |
cat ./kvrocks2redis-ci.conf | |
./build/kvrocks2redis -c ./kvrocks2redis-ci.conf | |
sleep 10s | |
python3 utils/kvrocks2redis/tests/populate-kvrocks.py --password="" --flushdb=true | |
sleep 10s | |
ps aux | |
python3 utils/kvrocks2redis/tests/check_consistency.py --src_password="" | |
- name: Find reports and crashes | |
if: always() | |
run: | | |
SANITIZER_OUTPUT=$(grep "Sanitizer:" tests/gocase/workspace -r || true) | |
if [[ $SANITIZER_OUTPUT ]]; then | |
echo "found sanitizer reports:" | |
echo "$SANITIZER_OUTPUT" | |
echo "detail log:" | |
cat $(echo "$SANITIZER_OUTPUT" | awk -F ':' '{print $1}') | |
exit 1 | |
fi | |
CRASHES=$(grep "Ooops!" tests/gocase/workspace -r || true) | |
if [[ $CRASHES ]]; then | |
echo "found crashes:" | |
echo "$CRASHES" | |
echo "detail log:" | |
cat $(echo "$CRASHES" | awk -F ':' '{print $1}') | |
exit 1 | |
fi | |
- uses: actions/upload-artifact@v4 | |
if: ${{ failure() && startsWith(matrix.os, 'ubuntu') }} | |
with: | |
name: kvrocks-coredumps-${{ matrix.name }} | |
path: | | |
./build/kvrocks | |
./coredumps/* | |
- name: Collect coverage into one XML report | |
if: ${{ matrix.sonarcloud }} | |
run: | | |
gcovr --sonarqube > ${{ env.SONARCLOUD_OUTPUT_DIR }}/coverage.xml | |
- name: Add event information | |
if: ${{ matrix.sonarcloud }} | |
env: | |
GITHUB_EVENT_JSON: ${{ toJson(github.event) }} | |
run: | | |
echo "$GITHUB_EVENT_JSON" | tee ${{ env.SONARCLOUD_OUTPUT_DIR }}/github-event.json | |
- name: Upload SonarCloud data | |
if: ${{ matrix.sonarcloud }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: sonarcloud-data | |
path: ${{ env.SONARCLOUD_OUTPUT_DIR }} | |
check-docker: | |
name: Check Docker image | |
needs: [precondition, check-and-lint, check-typos] | |
if: ${{ needs.precondition.outputs.docs_only != 'true' }} | |
runs-on: ubuntu-22.04 | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Get core numbers | |
run: echo "NPROC=$(nproc)" >> $GITHUB_ENV | |
- uses: docker/build-push-action@v5 | |
with: | |
context: . | |
build-args: MORE_BUILD_ARGS=-j${{ env.NPROC }} | |
push: false | |
tags: kvrocks:ci | |
outputs: type=docker | |
- name: Test built image | |
run: | | |
docker run --rm kvrocks:ci -v | |
ID="$(docker run --rm -d -p 6666:6666 kvrocks:ci)" | |
sleep 1m | |
if [ "$(docker inspect --format='{{.State.Health.Status}}' $ID)" != "healthy" ]; then | |
echo "The container is not healthy." | |
exit 1 | |
fi | |
if [ "$(ss --listening --no-header --tcp '( sport = :6666 )')" == "" ]; then | |
echo "The container listening port can not be accessed from outside." | |
exit 1 | |
fi | |
docker stop $ID | |
build-and-test-in-container: | |
name: Build and test in container | |
needs: [precondition, check-and-lint, check-typos] | |
if: ${{ needs.precondition.outputs.docs_only != 'true' }} | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
- name: CentOS 7 | |
image: centos:7 | |
compiler: gcc | |
- name: openSUSE Leap 15 | |
image: opensuse/leap:15 | |
compiler: gcc | |
- name: ArchLinux | |
image: archlinux:base | |
compiler: gcc | |
runs-on: ubuntu-22.04 | |
container: | |
image: ${{ matrix.image }} | |
steps: | |
- name: Setup CentOS | |
if: ${{ startsWith(matrix.image, 'centos') }} | |
run: | | |
yum install -y centos-release-scl-rh | |
yum install -y devtoolset-11 python3 python3-pip autoconf automake wget git gcc gcc-c++ | |
echo "NPROC=$(nproc)" >> $GITHUB_ENV | |
mv /usr/bin/gcc /usr/bin/gcc-4.8.5 | |
ln -s /opt/rh/devtoolset-11/root/bin/gcc /usr/bin/gcc | |
mv /usr/bin/g++ /usr/bin/g++-4.8.5 | |
ln -s /opt/rh/devtoolset-11/root/bin/g++ /usr/bin/g++ | |
- name: Setup ArchLinux | |
if: ${{ startsWith(matrix.image, 'archlinux') }} | |
run: | | |
pacman -Syu --noconfirm | |
pacman -Sy --noconfirm autoconf automake python3 python-redis git wget which cmake make gcc | |
echo "NPROC=$(nproc)" >> $GITHUB_ENV | |
- name: Setup openSUSE | |
if: ${{ startsWith(matrix.image, 'opensuse') }} | |
run: | | |
zypper install -y gcc11 gcc11-c++ make wget git autoconf automake python3 python3-pip curl tar gzip cmake go | |
update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-11 100 | |
update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-11 100 | |
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 | |
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100 | |
echo "NPROC=$(nproc)" >> $GITHUB_ENV | |
- name: Cache redis | |
id: cache-redis | |
uses: actions/cache@v3 | |
with: | |
path: | | |
~/local/bin/redis-cli | |
key: ${{ matrix.image }}-redis-cli | |
- name: Cache redis server | |
id: cache-redis-server | |
uses: actions/cache@v3 | |
with: | |
path: | | |
~/local/bin/redis-server | |
key: ${{ matrix.image }}-redis-server | |
- name: Install redis | |
if: ${{ steps.cache-redis.outputs.cache-hit != 'true' || steps.cache-redis-server.outputs.cache-hit != 'true' }} | |
run: | | |
curl -O https://download.redis.io/releases/redis-6.2.14.tar.gz | |
tar -xzvf redis-6.2.14.tar.gz | |
mkdir -p $HOME/local/bin | |
pushd redis-6.2.14 && USE_JEMALLOC=no make -j$NPROC redis-cli && mv src/redis-cli $HOME/local/bin/ && popd | |
pushd redis-6.2.14 && USE_JEMALLOC=no make -j$NPROC redis-server && mv src/redis-server $HOME/local/bin/ && popd | |
- name: Install cmake | |
if: ${{ startsWith(matrix.image, 'centos') }} | |
run: | | |
VERSION=3.26.4 | |
wget https://github.com/Kitware/CMake/releases/download/v$VERSION/cmake-$VERSION-linux-x86_64.sh | |
bash cmake-$VERSION-linux-x86_64.sh --skip-license --prefix=/usr | |
- uses: actions/checkout@v3 #v4 use Node 20 and not working at CentOS 7 | |
- uses: actions/setup-go@v4 #v5 use Node 20 too | |
if: ${{ !startsWith(matrix.image, 'opensuse') }} | |
with: | |
go-version-file: 'tests/gocase/go.mod' | |
cache: false | |
- name: Build Kvrocks | |
run: | | |
./x.py build -j$NPROC --unittest --compiler ${{ matrix.compiler }} | |
- name: Run Unit Test | |
run: | | |
./x.py test cpp | |
- name: Run Go Integration Cases | |
run: | | |
export PATH=$PATH:$HOME/local/bin/ | |
GOCASE_RUN_ARGS="" | |
./x.py test go build $GOCASE_RUN_ARGS | |
- name: Install redis-py | |
if: ${{ !startsWith(matrix.image, 'archlinux') }} # already installed | |
run: pip3 install redis==4.3.6 | |
- name: Run kvrocks2redis Test | |
run: | | |
$HOME/local/bin/redis-server --daemonize yes | |
mkdir -p kvrocks2redis-ci-data | |
./build/kvrocks --dir `pwd`/kvrocks2redis-ci-data --pidfile `pwd`/kvrocks.pid --daemonize yes | |
sleep 10s | |
echo -en "data-dir `pwd`/kvrocks2redis-ci-data\ndaemonize yes\noutput-dir ./\nnamespace.__namespace 127.0.0.1 6379\n" >> ./kvrocks2redis-ci.conf | |
cat ./kvrocks2redis-ci.conf | |
./build/kvrocks2redis -c ./kvrocks2redis-ci.conf | |
sleep 10s | |
python3 utils/kvrocks2redis/tests/populate-kvrocks.py --password="" --flushdb=true | |
sleep 10s | |
python3 utils/kvrocks2redis/tests/check_consistency.py --src_password="" | |
required: | |
if: always() | |
name: Required | |
runs-on: ubuntu-latest | |
needs: | |
- precondition | |
- build-and-test | |
- build-and-test-in-container | |
- check-docker | |
steps: | |
- name: Merge requirement checking | |
if: ${{ needs.precondition.outputs.docs_only != 'true' }} | |
run: | | |
if [[ ! ( \ | |
"${{ needs.build-and-test.result }}" == "success" \ | |
&& "${{ needs.build-and-test-in-container.result }}" == "success" \ | |
&& "${{ needs.check-docker.result }}" == "success" \ | |
) ]]; then | |
echo "Required jobs haven't been completed successfully." | |
exit 1 | |
fi | |
- name: Sentinel | |
run: true |