GHI #20 Fix workflow files #104
Workflow file for this run
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
name: Run All Tests | |
on: | |
pull_request: | |
branches: | |
- '**' | |
jobs: | |
test-native: | |
strategy: | |
matrix: | |
# verbose labels make things easier to read in GitHub Actions | |
# platform gets converted to os, preset, compiler, architecture | |
platform: | |
- windows-gcc-x86_64 | |
- windows-msvc-x86_32 | |
- windows-msvc-x86_64 | |
- macos-clang-x86_64 | |
- linux-clang-x86_64 | |
- linux-gcc-x86_64 | |
# convert verbose labels to usable values (which don't appear in GitHub Actions GUI) | |
include: | |
# platform -> os, preset, compiler, architecture | |
- platform: windows-gcc-x86_64 | |
os: windows | |
preset: patomic-ci-native-unix-gcc | |
architecture: x86_64 | |
- platform: windows-msvc-x86_32 | |
os: windows | |
preset: patomic-ci-native-win32-msvc | |
architecture: x86_32 | |
- platform: windows-msvc-x86_64 | |
os: windows | |
preset: patomic-ci-native-win64-msvc | |
architecture: x86_64 | |
- platform: macos-clang-x86_64 | |
os: macos | |
preset: patomic-ci-native-unix-clang | |
architecture: x86_64 | |
- platform: linux-clang-x86_64 | |
os: ubuntu | |
preset: patomic-ci-native-unix-clang | |
architecture: x86_64 | |
- platform: linux-gcc-x86_64 | |
os: ubuntu | |
preset: patomic-ci-native-unix-gcc | |
architecture: x86_64 | |
uses: doodspav/patomic/.github/workflows/reusable-test-native.yml@feature/ghi-20-inner-stuff | |
with: | |
os: ${{ matrix.os }} | |
preset: ${{ matrix.preset }} | |
architecture: ${{ matrix.architecture }} | |
# test-qemu: | |
# strategy: | |
# matrix: | |
# # architecture gets converted to triple | |
# # short form here so that it doesn't take up the whole GitHub Action name | |
# architecture: | |
# - aarch64 | |
# # - alpha todo: uncomment | |
# - arm | |
# - armhf | |
# # - hppa todo: uncomment | |
# # fixme: m68k is supported, but ICEs clang and segfaults qemu when built with gcc (GHI #25) | |
# # - m68k | |
# - mips | |
# - mips64 | |
# - mips64el | |
# - mipsel | |
# - ppc | |
# - ppc64 | |
# - ppc64le | |
# # - riscv64 todo: uncomment | |
# - s390x | |
# # fixme: sh4 is supported (only for gcc), but segfaults qemu when built with gcc (GHI #25) | |
# # - sh4 | |
# # - sparc64 todo:uncomment | |
# # - x86_32 todo: uncomment | |
# # convert architectures to triples | |
# include: | |
# - architecture: aarch64 | |
# triple: aarch64-linux-gnu | |
# # - architecture: alpha | |
# # triple: alpha-linux-gnu | |
# - architecture: arm | |
# triple: arm-linux-gnueabi | |
# - architecture: armhf | |
# triple: arm-linux-gnueabihf | |
# # - architecture: hppa | |
# # triple: hppa-linux-gnu | |
# # - architecture: m68k | |
# # triple: m68k-linux-gnu | |
# - architecture: mips | |
# triple: mips-linux-gnu | |
# - architecture: mips64 | |
# triple: mips64-linux-gnuabi64 | |
# - architecture: mips64el | |
# triple: mips64el-linux-gnuabi64 | |
# - architecture: mipsel | |
# triple: mipsel-linux-gnu | |
# - architecture: ppc | |
# triple: powerpc-linux-gnu | |
# - architecture: ppc64 | |
# triple: powerpc64-linux-gnu | |
# - architecture: ppc64le | |
# triple: powerpc64le-linux-gnu | |
# # - architecture: riscv64 | |
# # triple: riscv64-linux-gnu | |
# - architecture: s390x | |
# triple: s390x-linux-gnu | |
# # - architecture: sh4 | |
# # triple: sh4-linux-gnu | |
# # - architecture: sparc64 | |
# # triple: sparc64-linux-gnu | |
# # - architecture: x86_32 | |
# # triple: i686-linux-gnu | |
# uses: doodspav/patomic/.github/workflows/reusable-test-qemu.yml@feature/ghi-20-inner-stuff | |
# with: | |
# triple: ${{ matrix.triple }} | |
# architecture: ${{ matrix.architecture }} | |
# | |
# publish-results: | |
# runs-on: ubuntu-latest | |
# needs: | |
# - test-native | |
# - test-qemu | |
# | |
# steps: | |
# - name: Download Test Result Artifacts | |
# uses: actions/download-artifact@v3 | |
# with: | |
# name: test-results | |
# path: test-results/ | |
# | |
# - name: Publish Test Results | |
# uses: EnricoMi/publish-unit-test-result-action@v2 | |
# with: | |
# action_fail: true | |
# action_fail_on_inconclusive: true | |
# check_name: "Test Results: Warning" | |
# files: test-results/**/*.xml | |
publish-coverage: | |
runs-on: ubuntu-latest | |
needs: | |
- test-native | |
# - test-qemu | |
env: | |
HI_LIMIT: 100 | |
MED_LIMIT: 90 | |
steps: | |
- name: Install Dependencies | |
run: | | |
sudo apt update | |
sudo apt install binutils # for c++filt | |
sudo apt install lcov | |
- name: Download Test Coverage Artifacts | |
uses: actions/download-artifact@v3 | |
with: | |
name: test-coverage-internal | |
path: test-coverage-internal/ | |
- name: Checkout patomic | |
uses: actions/checkout@v4 | |
with: | |
path: patomic | |
- name: Copy patomic To All Original Mapping Paths | |
run: | | |
# go through all patomic.rootpath files | |
find ./test-coverage-internal -type f -name '*patomic.rootpath' -print0 | while IFS= read -r -d '' rp_file; do | |
root_path=$(cat "${rp_file}") | |
# create root_path if it doesn't exist | |
if [ ! -d "${root_path}" ]; then | |
sudo mkdir -p "${root_path}" | |
fi | |
# ensure full permissions even if it exists | |
sudo chmod -R a+rwx "${root_path}" | |
# copy patomic to root_path if not already there | |
if [ ! -d "${root_path}/patomic" ]; then | |
cp -R ./patomic/ "${root_path}/patomic" | |
fi | |
done | |
- name: Create Lcov Config File | |
run: | | |
touch ./lcovrc | |
echo "genhtml_hi_limit = ${{ env.HI_LIMIT }}" >> ./lcovrc | |
echo "genhtml_med_limit = ${{ env.MED_LIMIT }}" >> ./lcovrc | |
echo "genhtml_function_hi_limit = ${{ env.HI_LIMIT }}" >> ./lcovrc | |
echo "genhtml_function_med_limit = ${{ env.MED_LIMIT }}" >> ./lcovrc | |
echo "Contents of lcovrc:" | |
cat ./lcovrc | |
- name: Generate HTML Files | |
run: | | |
# directory into which to put user artifacts | |
mkdir test-coverage | |
# keep only coverage data relating to patomic source files | |
find ./test-coverage-internal -mindepth 1 -maxdepth 1 -type d -not -path '*/.*' -print0 | while IFS= read -r -d '' dir; do | |
mv "${dir}/patomic.lcov" "${dir}/patomic.lcov.old" | |
root_path=$(cat "${dir}/patomic.rootpath") | |
lcov --output-file "${dir}/patomic.lcov" --extract "${dir}/patomic.lcov.old" "${root_path}/patomic/src/*" | |
done | |
# generate html files for each separate compilation | |
find ./test-coverage-internal -mindepth 1 -maxdepth 1 -type d -not -path '*/.*' -print0 | while IFS= read -r -d '' dir; do | |
arch=$(basename "${dir}") | |
mkdir "./test-coverage/${arch}" | |
genhtml --output-directory "./test-coverage/${arch}" --title "patomic-${arch}" --show-details --num-spaces 4 --legend --demangle-cpp --config-file ./lcovrc --precision 2 "./test-coverage-internal/${arch}/patomic.lcov" | |
done | |
# generate html files for combined coverage | |
mkdir ./test-coverage/universal | |
lcov_combine_command=("lcov") | |
while IFS= read -r -d '' lcov_file; do | |
lcov_combine_command+=("-a" "${lcov_file}") | |
done < <(find ./test-coverage-internal -type f -name '*patomic.lcov' -print0) | |
lcov_combine_command+=("-o" "./patomic.lcov") | |
"${lcov_combine_command[@]}" | |
genhtml --output-directory "./test-coverage/universal" --title "patomic-universal" --show-details --num-spaces 4 --legend --demangle-cpp --config-file ./lcovrc --precision 2 ./patomic.lcov | |
- name: Upload Coverage Results | |
uses: actions/upload-artifact@v3 | |
with: | |
name: test-coverage | |
path: test-coverage/ | |
- name: Create Coverage Summary File | |
id: coverage-summary | |
shell: python | |
run: | | |
import dataclasses | |
import os | |
import pathlib | |
import re | |
import subprocess | |
@dataclasses.dataclass(frozen=True, kw_only=True) | |
class CoverageStats: | |
percent: float | |
count: int | |
total: int | |
if __name__ == "__main__": | |
# constants | |
HI_LEVEL = ${{ env.HI_LIMIT }} | |
MED_LEVEL = ${{ env.MED_LIMIT }} | |
BASE_DIR = pathlib.Path("./test-coverage-internal") | |
OUT_FILE = pathlib.Path("./coverage_summary.md") | |
# set up table | |
table_text = "| Target | Lines | Functions | Branches |\n" | |
table_text += "| :----- | ----: | --------: | -------: |\n" | |
# get all non-hidden directories in ./test-coverage | |
archs = [d for d in next(os.walk(BASE_DIR))[1] if d[0] != '.'] | |
archs.sort() | |
# keep track if any result is below the thresholds | |
any_warn = False | |
any_fail = False | |
# helper lambdas | |
make_emoji = lambda p: "✅" if p >= HI_LEVEL else ("⚠️" if p >= MED_LEVEL else "🛑") | |
stat_to_str = lambda s: f"{s.percent:.2f}% ({s.count}/{s.total}) {make_emoji(s.percent)}" | |
# add all coverage results to table | |
for arch in archs: | |
cov_data = {} | |
# get lcov summary | |
lcov_path = BASE_DIR / arch / "patomic.lcov" | |
res = subprocess.run(f'lcov --summary "{lcov_path}', capture_output=True, shell=True, check=True) | |
if res.returncode != 0: | |
raise RuntimeError(f"Command '{res.args.join(' ')}' returned non-zero exit status {res.returncode}, with stderr: {res.stderr.decode()}") | |
lcov_summary = res.stdout.decode() | |
# go through all lines of lcov summary | |
for line in lcov_summary.split('\n'): | |
# skip non-info lines | |
if not line.lstrip().startswith(("lines", "functions", "branches")): | |
continue | |
else: | |
attr_name = line.lstrip().split('.', maxsplit=1)[0] | |
# parse coverage stats | |
if "no data found" in line: | |
cov = CoverageStats(percent=100.0, count=0, total=0) | |
else: | |
pattern = r"(\d+\.\d+)% \((\d+) of (\d+)" | |
matches = re.findall(pattern, line)[0] | |
cov = CoverageStats(percent=float(matches[0]), count=int(matches[1]), total=int(matches[2])) | |
# store for next step | |
cov_data[attr_name] = cov | |
# check if we failed | |
any_warn = any_warn or cov_data[attr_name].percent < HI_LEVEL | |
any_fail = any_fail or cov_data[attr_name].percent < MED_LEVEL | |
# fill out table | |
table_text += f"| `{arch}` | `{stat_to_str(cov_data['lines'])}` | `{stat_to_str(cov_data['functions'])}` | `{stat_to_str(cov_data['branches'])}` |\n" | |
# write result to file | |
with open(OUT_FILE, "w") as f: | |
f.write(table_text) | |
# set output variables | |
subprocess.run(f"echo 'any_warn={int(any_warn)}' >> $GITHUB_OUTPUT", shell=True, check=True) | |
subprocess.run(f"echo 'any_warn={int(any_fail)}' >> $GITHUB_OUTPUT", shell=True, check=True) | |
- name: Publish Coverage in Action Summary | |
run: | | |
# TODO | |
- name: Publish Coverage in Pull Request Comment | |
uses: thollander/actions-comment-pull-request@v2 | |
with: | |
comment_tag: coverage-summary | |
filePath: coverage_summary.md | |
- name: Fail If Any Coverage Fails | |
run: | | |
if [[ '${{ steps.coverage-summary.outputs.any_fail }}' != '0' ]]; then | |
>&2 echo "At least one coverage result was below the threshold of ${{ env.MED_LIMIT }}" | |
exit 1 | |
fi | |
# keep this last so that we have it for debugging if something fails | |
- name: Delete Test Coverage Artifacts | |
uses: geekyeggo/delete-artifact@v2 | |
with: | |
name: test-coverage-internal |