forked from weaselab/conflict-set
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f30887f280 | ||
|
|
4dc5f7f75c | ||
|
|
e9c904a86b | ||
|
|
789ae8cbb9 | ||
|
|
d70e6a2455 | ||
|
|
8a5168f232 | ||
|
|
742d920aa1 | ||
|
|
6d8b939a81 | ||
|
|
48f9ee46cf | ||
|
|
f13a30e8aa | ||
|
|
1394a1a98f | ||
|
|
c52339a2ba | ||
|
|
3a82d90914 | ||
|
|
0921d8fedf | ||
|
|
f947d883e7 | ||
|
|
19f430d68f | ||
|
|
13e9e88e0e | ||
|
|
12a62a91cf | ||
|
|
cbbb23bf9d | ||
|
|
04d02261e9 | ||
|
|
dd8f006d3f | ||
|
|
776d06963b | ||
|
|
e03afe0651 | ||
|
|
6eecf6e4ac | ||
|
|
d74a12b5a4 | ||
|
|
755bbcbe56 | ||
|
|
2642d453dc |
@@ -0,0 +1,306 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-commit:
|
||||||
|
runs-on: ubuntu-latest-amd64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
. /etc/os-release
|
||||||
|
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||||
|
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y \
|
||||||
|
clang-21 git nodejs pre-commit
|
||||||
|
for tool in clang clang++; do
|
||||||
|
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
|
||||||
|
done
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pre-commit
|
||||||
|
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
|
|
||||||
|
- name: Run pre-commit
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory "$PWD"
|
||||||
|
pre-commit run --all-files --show-diff-on-failure
|
||||||
|
|
||||||
|
test:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: 64-bit-versions
|
||||||
|
cmake_args: -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1
|
||||||
|
- name: debug
|
||||||
|
cmake_args: -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
- name: simd-fallback
|
||||||
|
cmake_args: -DUSE_SIMD_FALLBACK=ON
|
||||||
|
- name: gcc
|
||||||
|
cmake_args: -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
|
||||||
|
runs-on: ubuntu-latest-amd64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
. /etc/os-release
|
||||||
|
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||||
|
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y \
|
||||||
|
build-essential ccache clang-21 cmake gcc g++ \
|
||||||
|
libc6-dbg llvm-21 lld-21 mold ninja-build python3 valgrind zstd
|
||||||
|
sudo curl -Ls "https://dl.min.io/client/mc/release/linux-amd64/mc" \
|
||||||
|
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
|
||||||
|
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
|
||||||
|
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
|
||||||
|
done
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .ccache
|
||||||
|
key: ccache-${{ matrix.name }}-${{ gitea.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
ccache-${{ matrix.name }}-
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
|
||||||
|
rm -rf build
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ${{ matrix.cmake_args }}
|
||||||
|
ninja -C build
|
||||||
|
ccache -s
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
|
||||||
|
|
||||||
|
- name: Upload test results to MinIO
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
||||||
|
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
|
||||||
|
run: |
|
||||||
|
if [ -z "$MINIO_ACCESS_KEY" ]; then
|
||||||
|
echo "MinIO credentials not configured; skipping upload"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
zstd build/Testing/*/Test.xml
|
||||||
|
mc cp build/Testing/*/Test.xml.zst "minio/jenkins/conflict-set/${{ gitea.run_number }}/${{ matrix.name }}/"
|
||||||
|
|
||||||
|
- name: Test summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
python3 ctest_summary.py build/Testing/*/Test.xml \
|
||||||
|
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/${{ matrix.name }}/Test.xml.zst" \
|
||||||
|
| tee -a "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
release:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: ubuntu-latest-amd64
|
||||||
|
arch: amd64
|
||||||
|
- runner: ubuntu-latest-arm64
|
||||||
|
arch: arm64
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: apt-${{ matrix.arch }}-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
. /etc/os-release
|
||||||
|
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||||
|
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y \
|
||||||
|
biber build-essential ccache clang-21 cmake devscripts \
|
||||||
|
latexmk libc6-dbg llvm-21 lld-21 mold ninja-build rpm \
|
||||||
|
texlive-bibtex-extra texlive-fonts-recommended \
|
||||||
|
texlive-latex-extra texlive-pictures valgrind zstd
|
||||||
|
sudo curl -Ls "https://dl.min.io/client/mc/release/linux-$(dpkg --print-architecture)/mc" \
|
||||||
|
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
|
||||||
|
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
|
||||||
|
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
|
||||||
|
done
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .ccache
|
||||||
|
key: ccache-release-${{ matrix.arch }}-${{ gitea.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
ccache-release-${{ matrix.arch }}-
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
|
||||||
|
rm -rf build
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_FLAGS=-DNVALGRIND
|
||||||
|
ninja -C build
|
||||||
|
ccache -s
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
# On arm64, valgrind needs the MAKE_MEM_DEFINED client requests for
|
||||||
|
# https://git.weaselab.dev/weaselab/conflict-set/issues/39, but this
|
||||||
|
# build has -DNVALGRIND, which compiles them out. Skip valgrind
|
||||||
|
# tests here; they run annotated in the test job.
|
||||||
|
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 ${{ matrix.arch == 'arm64' && '-E valgrind' || '' }} -T Test -j "$(nproc)" --timeout 90 > /dev/null
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
cpack -G DEB
|
||||||
|
cpack -G RPM
|
||||||
|
|
||||||
|
- name: Build paper
|
||||||
|
if: matrix.arch == 'amd64'
|
||||||
|
run: |
|
||||||
|
cd paper
|
||||||
|
make
|
||||||
|
|
||||||
|
- name: Upload artifacts to MinIO
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
||||||
|
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
|
||||||
|
run: |
|
||||||
|
if [ -z "$MINIO_ACCESS_KEY" ]; then
|
||||||
|
echo "MinIO credentials not configured; skipping upload"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
dest="minio/jenkins/conflict-set/${{ gitea.run_number }}/release-${{ matrix.arch }}/"
|
||||||
|
zstd build/Testing/*/Test.xml
|
||||||
|
mc cp build/Testing/*/Test.xml.zst "$dest"
|
||||||
|
if compgen -G "build/*.deb" > /dev/null; then
|
||||||
|
mc cp build/*.deb "$dest"
|
||||||
|
fi
|
||||||
|
if compgen -G "build/*.rpm" > /dev/null; then
|
||||||
|
mc cp build/*.rpm "$dest"
|
||||||
|
fi
|
||||||
|
if compgen -G "paper/*.pdf" > /dev/null; then
|
||||||
|
mc cp paper/*.pdf "$dest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Test summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
python3 ctest_summary.py build/Testing/*/Test.xml \
|
||||||
|
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/release-${{ matrix.arch }}/Test.xml.zst" \
|
||||||
|
| tee -a "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
runs-on: ubuntu-latest-amd64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
. /etc/os-release
|
||||||
|
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||||
|
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y \
|
||||||
|
build-essential ccache clang-21 cmake gcovr \
|
||||||
|
libc6-dbg llvm-21 lld-21 mold ninja-build python3 valgrind zstd
|
||||||
|
sudo curl -Ls "https://dl.min.io/client/mc/release/linux-amd64/mc" \
|
||||||
|
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
|
||||||
|
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
|
||||||
|
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
|
||||||
|
done
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .ccache
|
||||||
|
key: ccache-coverage-${{ gitea.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
ccache-coverage-
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
|
||||||
|
rm -rf build
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON
|
||||||
|
ninja -C build
|
||||||
|
ccache -s
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
|
||||||
|
|
||||||
|
- name: Coverage report
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
gcov_args=(-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h
|
||||||
|
--gcov-executable "llvm-cov gcov" --exclude-noncode-lines)
|
||||||
|
gcovr "${gcov_args[@]}" --cobertura > build/coverage.xml
|
||||||
|
gcovr "${gcov_args[@]}"
|
||||||
|
mkdir -p build/coverage_html
|
||||||
|
gcovr "${gcov_args[@]}" --html-details build/coverage_html/index.html
|
||||||
|
gcovr "${gcov_args[@]}" --fail-under-line 100 > /dev/null
|
||||||
|
|
||||||
|
- name: Upload results to MinIO
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
||||||
|
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
|
||||||
|
run: |
|
||||||
|
if [ -z "$MINIO_ACCESS_KEY" ]; then
|
||||||
|
echo "MinIO credentials not configured; skipping upload"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
dest="minio/jenkins/conflict-set/${{ gitea.run_number }}/coverage/"
|
||||||
|
zstd build/Testing/*/Test.xml
|
||||||
|
mc cp build/Testing/*/Test.xml.zst "$dest"
|
||||||
|
if [ -e build/coverage.xml ]; then
|
||||||
|
mc cp build/coverage.xml "$dest"
|
||||||
|
fi
|
||||||
|
if [ -d build/coverage_html ]; then
|
||||||
|
mc cp -r build/coverage_html "$dest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Test summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
python3 ctest_summary.py build/Testing/*/Test.xml \
|
||||||
|
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/coverage/Test.xml.zst" \
|
||||||
|
| tee -a "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "" | tee -a "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "📊 [Coverage report](https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/coverage/coverage_html/index.html)" | tee -a "$GITHUB_STEP_SUMMARY"
|
||||||
+31
-5
@@ -53,6 +53,7 @@ endif()
|
|||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||||
add_compile_options("-Wno-maybe-uninitialized")
|
add_compile_options("-Wno-maybe-uninitialized")
|
||||||
|
add_compile_options("-Wno-maybe-musttail-local-addr")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
@@ -60,12 +61,16 @@ if(NOT APPLE)
|
|||||||
add_compile_options(-g -fno-omit-frame-pointer)
|
add_compile_options(-g -fno-omit-frame-pointer)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
set(relro_flags "LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||||
|
set(full_relro_flags "-pie;${relro_flags}")
|
||||||
cmake_push_check_state()
|
cmake_push_check_state()
|
||||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
||||||
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
||||||
if(HAS_FULL_RELRO)
|
if(HAS_FULL_RELRO)
|
||||||
add_link_options(${full_relro_flags})
|
# -pie only applies to executables; passing it when linking a shared library
|
||||||
|
# makes the driver pull in Scrt1.o, which requires main.
|
||||||
|
add_link_options("$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:-pie>"
|
||||||
|
${relro_flags})
|
||||||
endif()
|
endif()
|
||||||
cmake_pop_check_state()
|
cmake_pop_check_state()
|
||||||
|
|
||||||
@@ -378,9 +383,30 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
find_program(HARDENING_CHECK hardening-check)
|
find_program(HARDENING_CHECK hardening-check)
|
||||||
if(HARDENING_CHECK)
|
if(HARDENING_CHECK)
|
||||||
add_test(NAME hardening_check
|
# Not all versions of hardening-check support the same options, so query
|
||||||
COMMAND ${HARDENING_CHECK} $<TARGET_FILE:${PROJECT_NAME}>
|
# the help output before using architecture-specific skips.
|
||||||
--nofortify --nostackprotector)
|
execute_process(
|
||||||
|
COMMAND ${HARDENING_CHECK} --help
|
||||||
|
OUTPUT_VARIABLE hardening_check_help
|
||||||
|
ERROR_VARIABLE hardening_check_help
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE)
|
||||||
|
set(hardening_check_arch_flags "")
|
||||||
|
# Control flow integrity (CET) is x86-only and branch protection (PAC/BTI)
|
||||||
|
# is arm64-only, so ignore whichever doesn't apply.
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 OR CMAKE_SYSTEM_PROCESSOR
|
||||||
|
STREQUAL arm64)
|
||||||
|
if(hardening_check_help MATCHES "nocfprotection")
|
||||||
|
list(APPEND hardening_check_arch_flags --nocfprotection)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
if(hardening_check_help MATCHES "nobranchprotection")
|
||||||
|
list(APPEND hardening_check_arch_flags --nobranchprotection)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
add_test(
|
||||||
|
NAME hardening_check
|
||||||
|
COMMAND ${HARDENING_CHECK} $<TARGET_FILE:${PROJECT_NAME}> --nofortify
|
||||||
|
--nostackprotector ${hardening_check_arch_flags})
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
+42
-12
@@ -52,6 +52,14 @@ limitations under the License.
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef __SANITIZE_ADDRESS__
|
||||||
|
#if defined(__has_feature)
|
||||||
|
#if __has_feature(address_sanitizer)
|
||||||
|
#define __SANITIZE_ADDRESS__
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <memcheck.h>
|
#include <memcheck.h>
|
||||||
|
|
||||||
using namespace weaselab;
|
using namespace weaselab;
|
||||||
@@ -2246,6 +2254,13 @@ bool checkMaxBetweenExclusiveImpl(Node3 *n, int begin, int end,
|
|||||||
mask |= inBounds(self->index[i]) << i;
|
mask |= inBounds(self->index[i]) << i;
|
||||||
}
|
}
|
||||||
mask &= (1 << self->numChildren) - 1;
|
mask &= (1 << self->numChildren) - 1;
|
||||||
|
#ifdef __aarch64__
|
||||||
|
// The bits surviving the mask above don't derive from uninitialized slots,
|
||||||
|
// but clang 21+ on aarch64 lowers inBounds through flags+csel, which
|
||||||
|
// memcheck models imprecisely, tainting bits the mask provably clears.
|
||||||
|
// https://git.weaselab.dev/weaselab/conflict-set/issues/39
|
||||||
|
VALGRIND_MAKE_MEM_DEFINED(&mask, sizeof(mask));
|
||||||
|
#endif
|
||||||
if (!mask) {
|
if (!mask) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2257,7 +2272,13 @@ bool checkMaxBetweenExclusiveImpl(Node3 *n, int begin, int end,
|
|||||||
compared |= (self->childMaxVersion[i] > readVersion) << i;
|
compared |= (self->childMaxVersion[i] > readVersion) << i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !(compared & mask) && firstRangeOk;
|
uint32_t compared_masked = compared & mask;
|
||||||
|
#ifdef __aarch64__
|
||||||
|
// Same imprecise csel modeling as above.
|
||||||
|
// https://git.weaselab.dev/weaselab/conflict-set/issues/39
|
||||||
|
VALGRIND_MAKE_MEM_DEFINED(&compared_masked, sizeof(compared_masked));
|
||||||
|
#endif
|
||||||
|
return !compared_masked && firstRangeOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool kAVX512>
|
template <bool kAVX512>
|
||||||
@@ -3048,7 +3069,12 @@ Node *firstGeqPhysical(Node *n, const TrivialSpan key) {
|
|||||||
#define MUSTTAIL
|
#define MUSTTAIL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if __has_attribute(preserve_none)
|
// ASan + preserve_none miscompiles the continuation chains on aarch64 with
|
||||||
|
// every clang tested (20, 21, trunk 22). The chains work with the default
|
||||||
|
// calling convention, so use that under ASan on aarch64.
|
||||||
|
// https://git.weaselab.dev/weaselab/conflict-set/issues/38
|
||||||
|
#if __has_attribute(preserve_none) && \
|
||||||
|
!(defined(__aarch64__) && defined(__SANITIZE_ADDRESS__))
|
||||||
#define PRESERVE_NONE __attribute__((preserve_none))
|
#define PRESERVE_NONE __attribute__((preserve_none))
|
||||||
#else
|
#else
|
||||||
#define PRESERVE_NONE
|
#define PRESERVE_NONE
|
||||||
@@ -4984,16 +5010,15 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
// encounter invalidated nodes.
|
// encounter invalidated nodes.
|
||||||
for (auto *iter = firstRangeWrite; iter != nullptr;
|
for (auto *iter = firstRangeWrite; iter != nullptr;
|
||||||
iter = iter->nextRangeWrite) {
|
iter = iter->nextRangeWrite) {
|
||||||
if (iter->endInsertionPoint != nullptr) {
|
assert(iter->endInsertionPoint != nullptr);
|
||||||
while (iter->insertionPoint->releaseDeferred) {
|
while (iter->insertionPoint->releaseDeferred) {
|
||||||
iter->insertionPoint = iter->insertionPoint->forwardTo;
|
iter->insertionPoint = iter->insertionPoint->forwardTo;
|
||||||
}
|
|
||||||
while (iter->endInsertionPoint->releaseDeferred) {
|
|
||||||
iter->endInsertionPoint = iter->endInsertionPoint->forwardTo;
|
|
||||||
}
|
|
||||||
eraseInRange(iter->insertionPoint, iter->endInsertionPoint,
|
|
||||||
&writeContext);
|
|
||||||
}
|
}
|
||||||
|
while (iter->endInsertionPoint->releaseDeferred) {
|
||||||
|
iter->endInsertionPoint = iter->endInsertionPoint->forwardTo;
|
||||||
|
}
|
||||||
|
eraseInRange(iter->insertionPoint, iter->endInsertionPoint,
|
||||||
|
&writeContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > kStackResultMax) [[unlikely]] {
|
if (count > kStackResultMax) [[unlikely]] {
|
||||||
@@ -5564,7 +5589,12 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
|||||||
: impl(std::exchange(other.impl, nullptr)) {}
|
: impl(std::exchange(other.impl, nullptr)) {}
|
||||||
|
|
||||||
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||||
impl = std::exchange(other.impl, nullptr);
|
if (this != &other) {
|
||||||
|
if (impl) {
|
||||||
|
internal_destroy(impl);
|
||||||
|
}
|
||||||
|
impl = std::exchange(other.impl, nullptr);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-25
@@ -8,48 +8,36 @@ RUN chmod -R 777 /tmp
|
|||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get upgrade -y
|
RUN apt-get upgrade -y
|
||||||
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||||
binutils-aarch64-linux-gnu \
|
biber \
|
||||||
build-essential \
|
build-essential \
|
||||||
ccache \
|
ccache \
|
||||||
|
clang \
|
||||||
cmake \
|
cmake \
|
||||||
curl \
|
curl \
|
||||||
devscripts \
|
devscripts \
|
||||||
g++-aarch64-linux-gnu \
|
|
||||||
gcovr \
|
gcovr \
|
||||||
git \
|
git \
|
||||||
gnupg \
|
latexmk \
|
||||||
libc6-dbg \
|
libc6-dbg \
|
||||||
lsb-release \
|
llvm \
|
||||||
mold \
|
mold \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
|
nodejs \
|
||||||
pre-commit \
|
pre-commit \
|
||||||
python3-requests \
|
python3-requests \
|
||||||
qemu-user \
|
|
||||||
rpm \
|
rpm \
|
||||||
software-properties-common \
|
texlive-bibtex-extra \
|
||||||
texlive-full \
|
texlive-fonts-recommended \
|
||||||
|
texlive-latex-extra \
|
||||||
|
texlive-pictures \
|
||||||
|
valgrind \
|
||||||
wget \
|
wget \
|
||||||
zstd
|
zstd
|
||||||
|
|
||||||
# Install recent valgrind from source
|
# MinIO client, for uploading build artifacts
|
||||||
RUN curl -Ls https://sourceware.org/pub/valgrind/valgrind-3.22.0.tar.bz2 -o valgrind.tar.bz2 && \
|
RUN curl -Ls "https://dl.min.io/client/mc/release/linux-$(dpkg --print-architecture)/mc" \
|
||||||
echo "c811db5add2c5f729944caf47c4e7a65dcaabb9461e472b578765dd7bf6d2d4c valgrind.tar.bz2" > valgrind-sha.txt && \
|
-o /usr/local/bin/mc && chmod +x /usr/local/bin/mc
|
||||||
sha256sum --quiet -c valgrind-sha.txt && \
|
|
||||||
mkdir valgrind && \
|
|
||||||
tar --strip-components 1 --no-same-owner --no-same-permissions --directory valgrind -xjf valgrind.tar.bz2 && \
|
|
||||||
cd valgrind && \
|
|
||||||
./configure --enable-only64bit --enable-lto && \
|
|
||||||
make -j`nproc` && \
|
|
||||||
make install && \
|
|
||||||
cd .. && \
|
|
||||||
rm -rf /tmp/*
|
|
||||||
|
|
||||||
# Recent clang
|
|
||||||
RUN wget https://apt.llvm.org/llvm.sh && chmod +x ./llvm.sh && ./llvm.sh 20
|
|
||||||
|
|
||||||
RUN apt-get -y install clang llvm
|
|
||||||
|
|
||||||
# Set after building valgrind, which doesn't build with clang for some reason
|
|
||||||
ENV CC=clang
|
ENV CC=clang
|
||||||
ENV CXX=clang++
|
ENV CXX=clang++
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -119,7 +119,13 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
|||||||
: impl(std::exchange(other.impl, nullptr)) {}
|
: impl(std::exchange(other.impl, nullptr)) {}
|
||||||
|
|
||||||
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||||
impl = std::exchange(other.impl, nullptr);
|
if (this != &other) {
|
||||||
|
if (impl) {
|
||||||
|
impl->~Impl();
|
||||||
|
safe_free(impl, sizeof(Impl));
|
||||||
|
}
|
||||||
|
impl = std::exchange(other.impl, nullptr);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
-151
@@ -1,151 +0,0 @@
|
|||||||
def CleanBuildAndTest(String cmakeArgs) {
|
|
||||||
sh """
|
|
||||||
export CCACHE_DIR=/ccache
|
|
||||||
rm -rf build
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake .. -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ${cmakeArgs}
|
|
||||||
ninja
|
|
||||||
ccache -s
|
|
||||||
"""
|
|
||||||
catchError {
|
|
||||||
sh '''
|
|
||||||
cd build
|
|
||||||
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j `nproc` --timeout 90 > /dev/null
|
|
||||||
zstd Testing/*/Test.xml
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
xunit tools: [CTest(pattern: 'build/Testing/*/Test.xml')], skipPublishingChecks: false
|
|
||||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/Testing/*/Test.xml.zst', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline {
|
|
||||||
agent any
|
|
||||||
stages {
|
|
||||||
stage('Pre-commit') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
env.HOME = env.WORKSPACE
|
|
||||||
}
|
|
||||||
sh 'pre-commit run --all-files --show-diff-on-failure'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('64 bit versions') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Debug') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('SIMD fallback') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Release [clang]') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
|
||||||
recordIssues(tools: [clang()])
|
|
||||||
sh '''
|
|
||||||
cd build
|
|
||||||
cpack -G DEB
|
|
||||||
cpack -G RPM
|
|
||||||
'''
|
|
||||||
sh '''
|
|
||||||
cd paper
|
|
||||||
make
|
|
||||||
'''
|
|
||||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm,paper/*.pdf', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('gcc') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++")
|
|
||||||
recordIssues(tools: [gcc()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Release [clang,aarch64]') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
|
||||||
sh '''
|
|
||||||
cd build
|
|
||||||
cpack -G DEB
|
|
||||||
cpack -G RPM
|
|
||||||
'''
|
|
||||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Coverage') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
gcov_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h --gcov-executable 'llvm-cov gcov' --exclude-noncode-lines"
|
|
||||||
}
|
|
||||||
CleanBuildAndTest("-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
|
||||||
sh """
|
|
||||||
gcovr ${gcov_args} --cobertura > build/coverage.xml
|
|
||||||
"""
|
|
||||||
recordCoverage qualityGates: [[criticality: 'NOTE', metric: 'MODULE']], tools: [[parser: 'COBERTURA', pattern: 'build/coverage.xml']]
|
|
||||||
sh """
|
|
||||||
gcovr ${gcov_args}
|
|
||||||
gcovr ${gcov_args} --fail-under-line 100 > /dev/null
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
emailext mimeType: 'text/html', body: '${SCRIPT, template="groovy-html.template"}', subject: "${env.JOB_NAME} - Build# ${env.BUILD_NUMBER} - ${currentBuild.currentResult}", to: 'andrew@weaselab.dev'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-1
@@ -981,7 +981,12 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
|||||||
: impl(std::exchange(other.impl, nullptr)) {}
|
: impl(std::exchange(other.impl, nullptr)) {}
|
||||||
|
|
||||||
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||||
impl = std::exchange(other.impl, nullptr);
|
if (this != &other) {
|
||||||
|
if (impl) {
|
||||||
|
internal_destroy(impl);
|
||||||
|
}
|
||||||
|
impl = std::exchange(other.impl, nullptr);
|
||||||
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class ConflictSet:
|
|||||||
ctypes.POINTER(ctypes.c_int),
|
ctypes.POINTER(ctypes.c_int),
|
||||||
ctypes.c_int,
|
ctypes.c_int,
|
||||||
)
|
)
|
||||||
|
self._lib.ConflictSet_check.restype = None
|
||||||
|
|
||||||
self._lib.ConflictSet_addWrites.argtypes = (
|
self._lib.ConflictSet_addWrites.argtypes = (
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
@@ -95,13 +96,16 @@ class ConflictSet:
|
|||||||
ctypes.c_int,
|
ctypes.c_int,
|
||||||
ctypes.c_int64,
|
ctypes.c_int64,
|
||||||
)
|
)
|
||||||
|
self._lib.ConflictSet_addWrites.restype = None
|
||||||
|
|
||||||
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
||||||
ctypes.c_void_p,
|
ctypes.c_void_p,
|
||||||
ctypes.c_int64,
|
ctypes.c_int64,
|
||||||
)
|
)
|
||||||
|
self._lib.ConflictSet_setOldestVersion.restype = None
|
||||||
|
|
||||||
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||||
|
self._lib.ConflictSet_destroy.restype = None
|
||||||
|
|
||||||
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
||||||
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Summarize a CTest Test.xml as markdown.
|
||||||
|
|
||||||
|
Intended for $GITHUB_STEP_SUMMARY in CI, where only the first few failures
|
||||||
|
are shown inline (pass --link to point at the full Test.xml). Also reusable
|
||||||
|
locally to print every failure from a downloaded Test.xml with --all.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import gzip
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
# Failure output is truncated to this many trailing characters, which is
|
||||||
|
# usually enough to include e.g. an ASan report's summary.
|
||||||
|
OUTPUT_TAIL_CHARS = 3000
|
||||||
|
|
||||||
|
|
||||||
|
def test_output(test):
|
||||||
|
value = test.find("./Results/Measurement/Value")
|
||||||
|
if value is None or value.text is None:
|
||||||
|
return ""
|
||||||
|
text = value.text
|
||||||
|
if value.get("encoding") == "base64":
|
||||||
|
raw = base64.b64decode(text)
|
||||||
|
if value.get("compression") == "gzip":
|
||||||
|
raw = gzip.decompress(raw)
|
||||||
|
text = raw.decode(errors="replace")
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("test_xml", help="path to a ctest Testing/*/Test.xml")
|
||||||
|
parser.add_argument(
|
||||||
|
"--inline",
|
||||||
|
type=int,
|
||||||
|
default=5,
|
||||||
|
help="how many failures to show inline (default 5)",
|
||||||
|
)
|
||||||
|
parser.add_argument("--all", action="store_true", help="show every failure inline")
|
||||||
|
parser.add_argument(
|
||||||
|
"--link", help="URL of the full Test.xml, linked when failures are elided"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
testing = ET.parse(args.test_xml).getroot().find("Testing")
|
||||||
|
tests = testing.findall("Test")
|
||||||
|
failed = [t for t in tests if t.get("Status") == "failed"]
|
||||||
|
notrun = sum(1 for t in tests if t.get("Status") == "notrun")
|
||||||
|
|
||||||
|
if not failed:
|
||||||
|
print(f"✅ All {len(tests) - notrun} tests passed")
|
||||||
|
else:
|
||||||
|
print(f"❌ {len(failed)} of {len(tests)} tests failed\n")
|
||||||
|
shown = failed if args.all else failed[: args.inline]
|
||||||
|
for test in shown:
|
||||||
|
name = test.findtext("Name")
|
||||||
|
output = test_output(test)[-OUTPUT_TAIL_CHARS:].strip()
|
||||||
|
print(f"<details><summary><code>{name}</code></summary>\n")
|
||||||
|
print("````")
|
||||||
|
print(output)
|
||||||
|
print("````")
|
||||||
|
print("</details>\n")
|
||||||
|
remaining = len(failed) - len(shown)
|
||||||
|
if remaining > 0:
|
||||||
|
more = f"… and {remaining} more"
|
||||||
|
if args.link:
|
||||||
|
more += f" — full list in [Test.xml]({args.link})"
|
||||||
|
print(more)
|
||||||
|
if notrun:
|
||||||
|
print(f"\n⚠️ {notrun} tests not run")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user