Repository: doodspav/patomic (language: python)

Issue:
Setup pipeline and presets
- create CMake presets
  - support code coverage
  - support -fsanitize
- run tests automatically
  - on all pushes to develop/main and all PRs
  - on all possible architectures
  - as many warnings enabled as possible (as errors)
  - generated code coverage, combine coverage from all tests, and fail under a certain % (also output statistic as a comment if possible)
  - merging a release branch into main should create a release and tag it
  - ~~maybe generate docs?~~ do this when setting up docs
- after:
  - require that all tests actually passed (in a separate stage)
  - require status checks to pass before merging on some branches
  - require deployments to succeed before merging on some branches


Discussion / hints:

Fix patch:
diff --git a/CMakePresets.json b/CMakePresets.json
index f10681631..3c0f96191 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -70,6 +70,16 @@
         "CMAKE_SHARED_LINKER_FLAGS_INIT": "-fprofile-instr-generate -fcoverage-mapping"
       }
     },
+    {
+      "name": "_patomic-ci-flags-coverage-gcc",
+      "hidden": true,
+      "cacheVariables": {
+        "CMAKE_C_FLAGS_INIT": "--coverage",
+        "CMAKE_CXX_FLAGS_INIT": "--coverage",
+        "CMAKE_EXE_LINKER_FLAGS_INIT": "--coverage",
+        "CMAKE_SHARED_LINKER_FLAGS_INIT": "--coverage"
+      }
+    },
     {
       "name": "_patomic-ci-flags-sanitize-gnu",
       "hidden": true,
@@ -106,12 +116,22 @@
     },
 
     {
-      "name": "patomic-ci-native-windows-msvc-warning",
+      "name": "patomic-ci-native-win32-msvc-warning",
       "inherits": [
         "_patomic-ci-base",
         "_patomic-ci-native-compiler-msvc",
         "_patomic-ci-flags-warning-msvc"
-      ]
+      ],
+      "architecture": "Win32"
+    },
+    {
+      "name": "patomic-ci-native-win64-msvc-warning",
+      "inherits": [
+        "_patomic-ci-base",
+        "_patomic-ci-native-compiler-msvc",
+        "_patomic-ci-flags-warning-msvc"
+      ],
+      "architecture": "x64"
     },
 
     {
@@ -155,6 +175,14 @@
         "_patomic-ci-flags-ansi-gnu"
       ]
     },
+    {
+      "name": "patomic-ci-native-unix-gcc-coverage",
+      "inherits": [
+        "_patomic-ci-base",
+        "_patomic-ci-native-compiler-gcc",
+        "_patomic-ci-flags-coverage-gcc"
+      ]
+    },
     {
       "name": "patomic-ci-native-unix-gcc-sanitize",
       "inherits": [
@@ -177,40 +205,28 @@
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-ansi-gnu"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "clang"
-      }
+      ]
     },
     {
       "name": "patomic-ci-qemu-ubuntu-clang-coverage",
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-coverage-clang"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "clang"
-      }
+      ]
     },
     {
       "name": "patomic-ci-qemu-ubuntu-clang-sanitize",
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-sanitize-gnu"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "clang"
-      }
+      ]
     },
     {
       "name": "patomic-ci-qemu-ubuntu-clang-warning",
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-warning-clang"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "clang"
-      }
+      ]
     },
 
     {
@@ -218,30 +234,28 @@
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-ansi-gnu"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "gcc"
-      }
+      ]
+    },
+    {
+      "name": "patomic-ci-qemu-ubuntu-gcc-coverage",
+      "inherits": [
+        "_patomic-ci-qemu-base",
+        "_patomic-ci-flags-coverage-gcc"
+      ]
     },
     {
       "name": "patomic-ci-qemu-ubuntu-gcc-sanitize",
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-sanitize-gnu"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "gcc"
-      }
+      ]
     },
     {
       "name": "patomic-ci-qemu-ubuntu-gcc-warning",
       "inherits": [
         "_patomic-ci-qemu-base",
         "_patomic-ci-flags-warning-gcc"
-      ],
-      "cacheVariables": {
-        "PATOMIC_CI_XCOMPILER": "gcc"
-      }
+      ]
     }
   ]
 }
diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake
index fd1315e8b..67f14ebec 100644
--- a/cmake/OptionVariables.cmake
+++ b/cmake/OptionVariables.cmake
@@ -122,7 +122,7 @@ include(GNUInstallDirs)
 # to DEC before calling find_package.
 set(
     PATOMIC_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}-${PROJECT_VERSION}"
-    CACHE STRING "CMake package config location relative to the install prefix"
+    CACHE STRING "CMake ${package_name} package's config location relative to the install prefix"
 )
 # depends on CMAKE_INSTALL_LIBDIR which is marked as advanced in GNUInstallDirs
 mark_as_advanced(PATOMIC_INSTALL_CMAKEDIR)


Test patch:
diff --git a/.github/actions/test-build-run/action.yml b/.github/actions/test-build-run/action.yml
deleted file mode 100644
index 21222f380..000000000
--- a/.github/actions/test-build-run/action.yml
+++ /dev/null
@@ -1,247 +0,0 @@
-name: Build and Run Tests
-description: Builds and runs all tests as one of { ansi, coverage, sanitize, warning }.
-
-inputs:
-  test_type:
-    description: 'One of { ansi, coverage, sanitize, warning }'
-    required: true
-    default: ''
-  preset:
-    description: 'CMake preset used to build patomic and googletest'
-    required: true
-    default: ''
-  architecture:
-    description: 'CPU architecture tests are run on, passed to QEMU'
-    required: true
-    default: ''
-  triple:
-    description: 'Platform triple to compile for, used to install correct compiler and sysroot'
-    required: true
-    default: ''
-  cmake_build_shared:
-    description: 'Determines value of BUILD_SHARED_LIBS for CMake, must be considered a boolean by CMake'
-    required: true
-    default: ''
-  patomic_path:
-    description: 'Source directory of patomic repository'
-    required: true
-    default: ''
-
-runs:
-  using: composite
-  steps:
-    - name: Check No Empty Inputs
-      shell: bash
-      run: |
-        # Check No Empty Inputs
-        
-        # checks input is not empty
-        check_input() {
-          input_name="${1}"
-          input_value="${2}"
-          if [[ -z "${input_value}" ]]; then
-            echo "Error: required input '${input_name}' is empty or was not specified."
-            exit 1
-          fi
-        }
-        
-        # apply function to all inputs
-        check_input "test_type" "${{ inputs.test_type }}"
-        check_input "preset" "${{ inputs.preset }}"
-        check_input "architecture" "${{ inputs.architecture }}"
-        check_input "triple" "${{ inputs.triple }}"
-        check_input "cmake_build_shared" "${{ inputs.cmake_build_shared }}"
-        check_input "patomic_path" "${{ inputs.patomic_path }}"
-
-    - name: Parse Input Values Into New Attributes
-      shell: bash
-      run: |
-        # Parse Input Values Into New Attributes
-        
-        # List of variables written to GITHUB_ENV:
-        # - ACTION_CMAKE_BUILD_TYPE
-        # - ACTION_OS
-        # - ACTION_COMPILER
-        # - ACTION_COMPILER_VERSION
-        # - ACTION_GCC_VERSION
-        # - ACTION_LLVM_VERSION
-        # - ACTION_BUILD_SHARED
-        # - ACTION_UNIQUE_CACHE_ID
-        # - ACTION_UNIQUE_FILE_NAME
-        # - ACTION_WORKING_DIR
-        
-        # cmake_build_type component - generate
-        if [[ "${{ inputs.test_type }}" == "ansi" ]]; then
-          cmake_build_type="Release"
-        elif [[ "${{ inputs.test_type }}" == "coverage" ]]; then
-          cmake_build_type="Debug"
-        elif [[ "${{ inputs.test_type }}" == "sanitize" ]]; then
-          cmake_build_type="Debug"
-        elif [[ "${{ inputs.test_type }}" == "warning" ]]; then
-          cmake_build_type="Release"
-        else
-          echo "Error: input 'test_type' has unknown value '${{ inputs.test_type }}'; must be one of 'ansi', 'coverage', 'sanitize', 'warning'."
-          exit 1
-        fi
-        
-        # cmake_build_type component - save
-        echo "ACTION_CMAKE_BUILD_TYPE: ${cmake_build_type}"
-        echo "ACTION_CMAKE_BUILD_TYPE=${cmake_build_type}" >> $GITHUB_ENV
-        
-        # os component - generate
-        if [[ "${{ runner.os }}" == "Linux" ]]; then
-          os="ubuntu"
-        elif [[ "${{ runner.os }}" == "macOS" ]]; then
-          os="macos"
-        elif [[ "${{ runner.os }}" == "Windows" ]]; then
-          os="windows"
-        else
-          echo "Error: running on unknown OS '${{ runner.os }}'; does not match one of 'Linux', 'macOS', or 'Windows'."
-          exit 1
-        fi
-        
-        # os component - save
-        echo "ACTION_OS: ${os}"
-        echo "ACTION_OS=${os}" >> $GITHUB_ENV
-        
-        # compiler component - generate
-        if [[ "${{ inputs.preset }}" == *"clang"* ]]; then
-          compiler="clang"
-        elif [[ "${{ inputs.preset }}" == *"gcc"* ]]; then
-          compiler="gcc"
-        elif [[ "${{ inputs.preset }}" == *"msvc"* ]]; then
-          compiler="msvc"
-        else
-          echo "Error: cannot determine known compiler from 'preset' value '${{ inputs.preset }}'; must contain one of 'clang', 'gcc', or 'msvc'."
-          exit 1
-        fi
-        
-        # compiler component - save
-        echo "ACTION_COMPILER: ${compiler}"
-        echo "ACTION_COMPILER=${compiler}" >> $GITHUB_ENV
-        
-        # compiler version component - generate
-        gcc_version="ACTION_GCC_VERSION_not_set_on_native"
-        llvm_version="ACTION_LLVM_VERSION_not_set_on_native"
-        if [[ "${{ inputs.preset }}" == *"qemu"* ]]; then
-          gcc_version="11"
-          llvm_version="15"
-          if [[ "${compiler}" == "clang" ]]; then
-            compiler_version="${clang_version}"
-          elif [[ "${compiler}" == "gcc" ]]; then
-            compiler_version="${gcc_version}"
-          else
-            echo "Error: only 'clang' and 'gcc' compilers are valid when running with qemu preset."
-            exit 1
-          fi
-        else
-          compiler_version="ACTION_COMPILER_VERSION_not_set_on_native"
-        fi
-        
-        # compiler version component - save
-        echo "ACTION_COMPILER_VERSION: ${compiler_version}"
-        echo "ACTION_COMPILER_VERSION=${compiler_version}" >> $GITHUB_ENV
-        
-        # individual compiler version component - generate, save
-        echo "ACTION_GCC_VERSION: ${gcc_version}"
-        echo "ACTION_GCC_VERSION=${gcc_version}" >> $GITHUB_ENV
-        echo "ACTION_LLVM_VERSION: ${llvm_version}"
-        echo "ACTION_LLVM_VERSION=${llvm_version}" >> $GITHUB_ENV
-        
-        # build_shared component - generate
-        true_values=("1" "on" "yes" "true" "y")
-        false_values=("0" "off" "no" "false" "n")
-        cmake_build_shared_lower=$(echo "${{ inputs.cmake_build_shared }}" | tr '[:upper:]' '[:lower:]')
-        if [[ " ${true_values[@]} " =~ " ${cmake_build_shared_lower} " ]]; then
-          build_shared="shared"
-        elif [[ "${false_values[@]} " =~ " ${cmake_build_shared_lower} " ]]; then
-          build_shared="static"
-        else
-          echo "Error: input 'cmake_build_shared' value is '${{ inputs.cmake_build_shared }}', must be one of: ${true_values[*]} ${false_values[*]} (case-insensitive)."
-          exit 1
-        fi
-        
-        # build_shared component - save
-        echo "ACTION_BUILD_SHARED: ${build_shared}"
-        echo "ACTION_BUILD_SHARED=${build_shared}" >> $GITHUB_ENV
-        
-        # unique_cache_id component - generate, save
-        unique_cache_id="${{ inputs.triple }}-${os}-${compiler}-${build_shared}"
-        echo "ACTION_UNIQUE_CACHE_ID: ${unique_cache_id}"
-        echo "ACTION_UNIQUE_CACHE_ID=${unique_cache_id}" >> $GITHUB_ENV
-        
-        # unique_file_name component - generate, save
-        # this is separate because:
-        # - we don't want the whole triple in file names
-        # - we may need to change this if we support multiple ABIs
-        # - separate from cache-id so we can change it without affecting caches
-        unique_file_name="${{ inputs.architecture }}-${os}-${compiler}-${build_shared}"
-        echo "ACTION_UNIQUE_FILE_NAME: ${unique_file_name}"
-        echo "ACTION_UNIQUE_FILE_NAME=${unique_file_name}" >> $GITHUB_ENV
-        
-        # working_dir component - generate, save
-        working_dir="${{ runner.temp }}/${unique_cache_id}"
-        mkdir -p "${working_dir}"
-        echo "ACTION_WORKING_DIR: ${working_dir}"
-        echo "ACTION_WORKING_DIR=${working_dir}" >> $GITHUB_ENV
-
-    - name: Install Cross-Compiling Toolchain
-      if: runner.os == 'Linux' && contains(inputs.preset, 'qemu')
-      shell: bash
-      run: |
-        # Install Cross-Compiling Toolchain
-        
-        sudo apt update
-        sudo apt install g++-${{ env.ACTION_GCC_VERSION }}-multilib
-        sudo apt install g++-${{ env.ACTION_GCC_VERSION }}-${{ inputs.triple }}
-        sudo apt install llvm-${{ env.ACTION_LLVM_VERSION }}
-        sudo apt install clang-${{ env.ACTION_LLVM_VERSION }}
-        sudo apt install qemu-user
-
-    - name: Restore Cached Sysroot (with GoogleTest)
-      id: cache-sysroot
-      uses: actions/cache@v3
-      with:
-        path: ${{ env.ACTION_WORKING_DIR }}/sysroot
-        key: action-test-build-run-${{ env.ACTION_UNIQUE_CACHE_ID }}
-
-    - name: Set Up Sysroot
-      if: runner.os == 'Linux' && contains(inputs.preset, 'qemu') && steps.cache-sysroot.outputs.cache-hit != 'true'
-      shell: bash
-      # cannot use symlink or reference directory directly, will not work
-      run: |
-        # Set Up Sysroot
-        
-        cp -r /usr/${{ inputs.triple }}/ ${{ env.ACTION_WORKING_DIR }}/sysroot
-
-    - name: Checkout GoogleTest
-      if: steps.cache-sysroot.outputs.cache-hit != 'true'
-      uses: actions/checkout@v4
-      with:
-        repository: google/googletest
-        # paths passed to actions/checkout are prefixed with $GITHUB_WORKSPACE
-        path: googletest-action-temp-${{ env.ACTION_UNIQUE_CACHE_ID }}
-
-    - name: Move GoogleTest to Working Directory
-      if: steps.cache-sysroot.outputs.cache-hit != 'true'
-      shell: bash
-      run: |
-        # Move GoogleTest to Working Directory
-        
-        cp -r ${{ github.workspace }}/googletest-action-temp-${{ env.ACTION_UNIQUE_CACHE_ID }}/ ${{ env.ACTION_WORKING_DIR }}/googletest
-        rm -rf ${{ github.workspace }}/googletest-action-temp-${{ env.ACTION_UNIQUE_CACHE_ID }}
-
-    - name: Build and Install GoogleTest
-      if: steps.cache-sysroot.outputs.cache-hit != 'true'
-      shell: bash
-      run: |
-        # Build and Install GoogleTest
-        
-        cd ${{ env.ACTION_WORKING_DIR }}/googletest
-        cp -r ${{ github.workspace }}/${{ inputs.patomic_path }}/ci/ ./
-        cp ${{github.workspace }}/${{ inputs.patomic_path }}/CMakePresets.json .
-        mkdir build
-        cd build
-        cmake --preset ${{ inputs.preset }} -DCMAKE_CXX_FLAGS="" -DBUILD_SHARED_LIBS=${{ inputs.cmake_build_shared }} -DCMAKE_BUILD_TYPE=${{ env.ACTION_CMAKE_BUILD_TYPE }} -Dgtest_force_shared_crt=ON -Dgtest_hide_internal_symbols=ON ..
-        cmake --build . --verbose
-        cmake --install . --prefix ${{ env.ACTION_WORKING_DIR }}/sysroot
diff --git a/.github/workflows/reusable-test-native.yml b/.github/workflows/reusable-test-native.yml
new file mode 100644
index 000000000..7c23cd7e6
--- /dev/null
+++ b/.github/workflows/reusable-test-native.yml
@@ -0,0 +1,251 @@
+name: Run Tests on Native Platforms
+
+on:
+  workflow_call:
+    inputs:
+      os:
+        description: 'Used to set the runs-on attribute, must be one of macos, ubuntu, windows'
+        required: true
+        type: string
+        default: ''
+      preset:
+        description: 'CMake preset used to build patomic and googletest, will be conditionally suffixed with one of ansi, coverage, sanitize, warning'
+        required: true
+        type: string
+        default: ''
+      architecture:
+        description: 'CPU architecture tests are run on, used for naming (does not affect how tests are run)'
+        required: true
+        type: string
+        default: ''
+
+jobs:
+  check:
+    runs-on: ubuntu-latest
+    
+    steps:
+      - name: Check No Empty Inputs
+        shell: bash
+        run: |
+          # checks input is not empty
+          check_input() {
+            input_name="${1}"
+            input_value="${2}"
+            if [[ -z "${input_value}" ]]; then
+              echo "Error: required input '${input_name}' is empty or was not specified."
+              exit 1
+            else
+              echo "${input_name}: ${input_value}"
+            fi
+          }
+
+          # apply function to all inputs
+          check_input "os"           "${{ inputs.os }}"
+          check_input "preset"       "${{ inputs.preset }}"
+          check_input "architecture" "${{ inputs.architecture }}"
+
+  run:
+    needs: check
+    runs-on: ${{ inputs.os }}-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        kind:
+          - ansi
+          - coverage
+          - sanitize
+          - warning
+        build_shared:
+          - static
+          - shared
+        include:
+          # hidden single variable
+          - compiler: ${{ contains(inputs.preset, 'clang') && 'clang' || ( contains(inputs.preset, 'gcc') && 'gcc' || 'msvc' ) }}
+          # alias variables
+          - build_shared: static
+            cmake_build_shared: OFF
+          - build_shared: shared
+            cmake_build_shared: ON
+    env:
+      # these have to be in env context because they depend on matrix context
+      UNIQUE_ID: ${{ matrix.kind }}-${{ inputs.architecture }}-${{ inputs.os }}-${{ matrix.compiler }}-${{ matrix.build_shared }}
+      UNIQUE_NAME: ${{ inputs.architecture }}-${{ inputs.os }}-${{ matrix.compiler }}-${{ matrix.build_shared }}
+      CMAKE_PRESET: ${{ inputs.preset }}-${{ matrix.kind }}
+      CMAKE_BUILD_TYPE: ${{ ( matrix.kind == 'ansi' || matrix.kind == 'warning' ) && 'Release' || 'Debug' }}
+      # currently not possible to skip job using matrix context, so this is the next best thing
+      SKIP_JOB: >-
+        ${{ (
+          ( matrix.kind == 'ansi' && matrix.compiler == 'msvc' )  ||
+          ( matrix.kind == 'coverage' && inputs.os == 'windows' ) ||
+          ( matrix.kind == 'sanitize' && inputs.os == 'windows' )
+        ) }}
+      # this job supports multiple os, each with their own root path
+      # this screws up lcov when it tries to merge the coverage path mappings
+      # we can't just modify mappings because:
+      # - GitHub issue #65006 on llvm/llvm-project suggests to NOT use -fcoverage-prefix-map
+      # - macos runner has AppleClang 14 which doesn't support -fcoverage-compilation-dir
+      # workaround is to have a fixed root path that everyone works from
+      # use /Users as root dir because /home is not supported on macos
+      ROOT_PATH: /Users/${{ matrix.kind }}
+
+    steps:
+      - name: Set Up Root Path
+        if: env.SKIP_JOB != 'true'
+        shell: bash
+        run: |
+          # macos and ubuntu need sudo, windows does not
+          prefix=""
+          if [[ "${{ inputs.os }}" != "windows" ]]; then
+            prefix="sudo "
+          fi
+          
+          ${prefix}mkdir -p "${{ env.ROOT_PATH }}"
+          ${prefix}chmod -R a+rwx "${{ env.ROOT_PATH }}"
+
+      - name: Checkout patomic
+        if: env.SKIP_JOB != 'true'
+        uses: actions/checkout@v4
+        with:
+          # cannot prefix with ROOT_PATH here, so copy over in next step
+          path: patomic
+
+      - name: Move patomic to Root Path Location
+        if: env.SKIP_JOB != 'true'
+        run: |
+          cp -R ./patomic/ "${{ env.ROOT_PATH }}/patomic"
+
+      - name: Restore Cached GoogleTest
+        if: env.SKIP_JOB != 'true'
+        id: cache-googletest
+        uses: actions/cache@v3
+        with:
+          path: ${{ env.ROOT_PATH }}/googletest/build/install
+          key: googletest-${{ env.UNIQUE_ID }}
+
+      - name: Checkout GoogleTest
+        if: env.SKIP_JOB != 'true' && steps.cache-googletest.outputs.cache-hit != 'true'
+        uses: actions/checkout@v4
+        with:
+          repository: google/googletest
+          # cannot prefix with ROOT_PATH here, so copy over in next step
+          path: googletest
+
+      - name: Move GoogleTest to Root Path Location
+        if: env.SKIP_JOB != 'true' && steps.cache-googletest.outputs.cache-hit != 'true'
+        run: |
+          cp -R ./googletest/ "${{ env.ROOT_PATH }}/googletest"
+
+      - name: Build and Install GoogleTest
+        if: env.SKIP_JOB != 'true' && steps.cache-googletest.outputs.cache-hit != 'true'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          cd googletest
+          cp ../patomic/CMakePresets.json .
+          mkdir build
+          cd build
+          cmake --preset ${{ env.CMAKE_PRESET }} -DBUILD_TESTING=OFF -DCMAKE_CXX_FLAGS="" -DBUILD_SHARED_LIBS=${{ matrix.cmake_build_shared }} -DCMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} -Dgtest_force_shared_crt=ON -Dgtest_hide_internal_symbols=ON ..
+          cmake --build . --verbose --config ${{ env.CMAKE_BUILD_TYPE }}
+          cmake --install . --config ${{ env.CMAKE_BUILD_TYPE }} --prefix install
+
+      # TODO: figure out how to cache this for builds where we don't change any source code
+      - name: Build patomic
+        if: env.SKIP_JOB != 'true'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          cd patomic
+          mkdir build
+          cd build
+          cmake --preset ${{ env.CMAKE_PRESET }} -DBUILD_SHARED_LIBS=${{ matrix.cmake_build_shared }} -DCMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} -DGTest_ROOT:PATH="../../googletest/build/install" ..
+          cmake --build . --verbose --config ${{ env.CMAKE_BUILD_TYPE }}
+      
+      - name: Test patomic
+        if: env.SKIP_JOB != 'true'
+        continue-on-error: true
+        env:
+          LABEL_REGEX: ${{ matrix.kind == 'coverage' && '^(ut)$' || '^(.*)$' }}
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          cd patomic/build
+          ctest --label-regex "${{ env.LABEL_REGEX }}" --verbose --output-junit Testing/Temporary/results.xml --build-config ${{ env.CMAKE_BUILD_TYPE }} .
+
+      - name: Prepare Test Results
+        if: env.SKIP_JOB != 'true'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          mkdir -p upload/test/${{ matrix.kind }}
+          python3 patomic/test/improve_ctest_xml.py --input patomic/build/Testing/Temporary/results.xml --triple ${{ matrix.kind }}-${{ env.UNIQUE_NAME }} --output upload/test/${{ matrix.kind }}/${{ env.UNIQUE_NAME }}.xml
+
+      - name: Upload Test Results
+        if: env.SKIP_JOB != 'true'
+        uses: actions/upload-artifact@v3
+        with:
+          name: test-results
+          path: ${{ env.ROOT_PATH }}/upload/test/
+
+      - name: Generate Lcov Tracefile and Root Path File (clang)
+        if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage' && matrix.compiler == 'clang'
+        shell: bash
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          # set up directory
+          mkdir -p upload/cov/${{ env.UNIQUE_NAME }}
+          cd patomic/build
+          
+          # macos needs xcrun to help use xcode to run llvm tools
+          # ubuntu needs llvm tools to be installed
+          prefix=""
+          if [[ "${{ inputs.os }}" == "macos" ]]; then
+            prefix="xcrun "
+          else  # [[ "${{ inputs.os }}" == "ubuntu" ]]; then
+            sudo apt install llvm
+          fi
+
+          # merge coverage output from all tests
+          # use find because bash on GitHub Actions currently does not support '**'
+          find test/working -type f -name "*.profraw" -print0 | xargs -0 ${prefix}llvm-profdata merge -output=patomic.profdata
+          
+          # convert profdata to lcov tracefile, and copy to upload
+          lib_files=(libpatomic.*)  # matches shared/static lib files and symlinks
+          ${prefix}llvm-cov export -format=lcov -instr-profile=patomic.profdata -object="${lib_files[0]}" >> patomic.lcov
+          cp patomic.lcov ../../upload/cov/${{ env.UNIQUE_NAME }}/patomic.lcov
+          
+          # we need original source mapping to make use of lcov tracefile
+          # we can't just modify mapping because:
+          # - GitHub issue #65006 on llvm/llvm-project suggests to NOT use -fcoverage-prefix-map
+          # - macos runner has AppleClang 14 which doesn't support -fcoverage-compilation-dir
+          # workaround is to store root path to help use coverage directory mappings later
+          cd ../..
+          echo "$PWD" >> upload/cov/${{ env.UNIQUE_NAME }}/patomic.rootpath
+
+      - name: Generate Lcov Tracefile and Root Path File (gcc)
+        if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage' && matrix.compiler == 'gcc'
+        shell: bash
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          # install lcov
+          sudo apt install lcov
+          
+          # set up directory
+          mkdir -p upload/cov/${{ env.UNIQUE_NAME }}
+          
+          # merge all .gcda files into an lcov tracefile, and copy to upload
+          # all tests have .gcno, but only tests that were executed have .gcda
+          lcov --directory --rc lcov_branch_coverage=1 patomic/build --capture --output-file patomic.lcov
+          cp patomic.lcov upload/cov/${{ env.UNIQUE_NAME }}/patomic.lcov
+          
+          # we need root path file because next job needs it because clang (above) needs it
+          # we might also need it for gcc, but at the moment i have no idea
+          echo "$PWD" >> upload/cov/${{ env.UNIQUE_NAME }}/patomic.rootpath
+
+      - name: Upload Internal Coverage Results
+        if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage'
+        uses: actions/upload-artifact@v3
+        with:
+          name: test-coverage-internal
+          path: ${{ env.ROOT_PATH }}/upload/cov/
diff --git a/.github/workflows/reusable-test-qemu.yml b/.github/workflows/reusable-test-qemu.yml
new file mode 100644
index 000000000..f5501c7ed
--- /dev/null
+++ b/.github/workflows/reusable-test-qemu.yml
@@ -0,0 +1,276 @@
+name: Run Tests on Emulated Platforms
+
+on:
+  workflow_call:
+    inputs:
+      triple:
+        description: 'Platform triple to compile for, used to install correct compiler and sysroot'
+        required: true
+        type: string
+        default: ''
+      architecture:
+        description: 'CPU architecture tests are run on, passed to QEMU'
+        required: true
+        type: string
+        default: ''
+      gcc_version:
+        description: 'Major version of GCC toolchain to use'
+        required: false
+        type: number
+        default: 10
+      llvm_version:
+        description: 'Major version of LLVM toolchain to use'
+        required: false
+        type: number
+        default: 15
+      skip_gcc:
+        description: "Conditionally skip 'run' job if gcc does not support the triple"
+        required: false
+        type: boolean
+        default: false
+      skip_llvm:
+        description: "Conditionally skip 'run' job if llvm does not support the triple"
+        required: false
+        type: boolean
+        default: false
+
+jobs:
+  check:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Check No Empty Inputs
+        shell: bash
+        run: |
+          # checks input is not empty
+          check_input() {
+            input_name="${1}"
+            input_value="${2}"
+            if [[ -z "${input_value}" ]]; then
+              echo "Error: required input '${input_name}' is empty or was not specified."
+              exit 1
+            else
+              echo "${input_name}: ${input_value}"
+            fi
+          }
+
+          # apply function to all inputs
+          check_input "triple"       "${{ inputs.triple }}"
+          check_input "architecture" "${{ inputs.architecture }}"
+
+  run:
+    needs: check
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        compiler:
+          - clang
+          - gcc
+        kind:
+          - ansi
+          - coverage
+          - sanitize
+          - warning
+        build_shared:
+          - static
+          - shared
+        exclude:
+          # fixme: clang can't find runtime coverage libraries (GHI #28)
+          - kind: coverage
+            compiler: clang
+          # fixme: not sure which archs asan/ubsan support (GHI #29)
+          - kind: sanitize
+        include:
+          # alias variables
+          - compiler: clang
+            compiler_version: ${{ inputs.llvm_version }}
+            preset: patomic-ci-qemu-ubuntu-clang
+          - compiler: gcc
+            compiler_version: ${{ inputs.gcc_version }}
+            preset: patomic-ci-qemu-ubuntu-gcc
+          - build_shared: static
+            cmake_build_shared: OFF
+          - build_shared: shared
+            cmake_build_shared: ON
+    env:
+      # required by toolchain file
+      PATOMIC_CI_XARCH: ${{ inputs.architecture }}
+      PATOMIC_CI_XTRIPLE: ${{ inputs.triple }}
+      PATOMIC_CI_XCOMPILER: ${{ matrix.compiler }}
+      PATOMIC_CI_XCOMPILER_VERSION: ${{ matrix.compiler_version }}
+      PATOMIC_CI_SYSROOT: /Users/${{ matrix.kind }}/sysroot
+      # these have to be in env context because they depend on matrix context
+      UNIQUE_ID: ${{ matrix.kind }}-${{ inputs.triple }}-ubuntu-${{ matrix.compiler }}-${{ matrix.build_shared }}-${{ inputs.gcc_version }}-${{ inputs.llvm_version }}
+      UNIQUE_NAME: ${{ inputs.architecture }}-ubuntu-${{ matrix.compiler }}-${{ matrix.build_shared }}
+      CMAKE_PRESET: ${{ matrix.preset }}-${{ matrix.kind }}
+      CMAKE_BUILD_TYPE: ${{ ( matrix.kind == 'ansi' || matrix.kind == 'warning' ) && 'Release' || 'Debug' }}
+      # apparently not possible to skip job using matrix context, so this is the next best thing
+      SKIP_JOB: >-
+        ${{ (
+          ( matrix.compiler == 'clang' && inputs.skip_llvm ) ||
+          ( matrix.compiler == 'gcc' && inputs.skip_gcc )
+        ) }}
+      # necessary because publish-coverage job expects everything to have the same root path
+      ROOT_PATH: /Users/${{ matrix.kind }}
+
+    steps:
+      - name: Install Toolchains and Qemu
+        if: env.SKIP_JOB != 'true'
+        run: |
+          sudo apt update
+          sudo apt install g++-${{ inputs.gcc_version }}-multilib
+          sudo apt install g++-${{ inputs.gcc_version }}-${{ inputs.triple }}
+          if [[ '${{ matrix.compiler }}' == 'clang' ]]; then
+            sudo apt install llvm-${{ inputs.llvm_version }}
+            sudo apt install clang-${{ inputs.llvm_version }}
+          fi
+          sudo apt install qemu-user
+
+      - name: Set Up Root Path
+        if: env.SKIP_JOB != 'true'
+        run: |
+          sudo mkdir -p "${{ env.ROOT_PATH }}"
+          sudo chmod -R a+rwx "${{ env.ROOT_PATH }}"
+
+      - name: Checkout patomic
+        if: env.SKIP_JOB != 'true'
+        uses: actions/checkout@v4
+        with:
+          # cannot prefix with ROOT_PATH here, so copy over in next step
+          path: patomic
+
+      - name: Move patomic to Root Path Location
+        if: env.SKIP_JOB != 'true'
+        run: |
+          cp -R ./patomic/ "${{ env.ROOT_PATH }}/patomic"
+
+      - name: Restore Cached Sysroot (with GoogleTest)
+        if: env.SKIP_JOB != 'true'
+        id: cache-sysroot
+        uses: actions/cache@v3
+        with:
+          path: ${{ env.ROOT_PATH }}/sysroot
+          key: sysroot-${{ env.UNIQUE_ID }}
+
+      - name: Set Up Sysroot
+        if: env.SKIP_JOB != 'true' && steps.cache-sysroot.outputs.cache-hit != 'true'
+        run: |
+          cp -R /usr/${{ inputs.triple }}/ ${{ env.ROOT_PATH }}/sysroot
+
+      - name: Checkout GoogleTest
+        if: env.SKIP_JOB != 'true' && steps.cache-sysroot.outputs.cache-hit != 'true'
+        uses: actions/checkout@v4
+        with:
+          repository: google/googletest
+          # cannot prefix with ROOT_PATH here, so copy over in next step
+          path: googletest
+
+      - name: Move GoogleTest to Root Path Location
+        if: env.SKIP_JOB != 'true' && steps.cache-sysroot.outputs.cache-hit != 'true'
+        run: |
+          cp -R ./googletest/ "${{ env.ROOT_PATH }}/googletest"
+
+      - name: Build and Install GoogleTest
+        if: env.SKIP_JOB != 'true' && steps.cache-sysroot.outputs.cache-hit != 'true'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          cd googletest
+          cp -R ../patomic/ci/ ./
+          cp ../patomic/CMakePresets.json .
+          mkdir build
+          cd build
+          cmake --preset ${{ env.CMAKE_PRESET }} -DBUILD_TESTING=OFF -DCMAKE_CXX_FLAGS="" -DBUILD_SHARED_LIBS=${{ matrix.cmake_build_shared }} -DCMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} -Dgtest_force_shared_crt=ON -Dgtest_hide_internal_symbols=ON ..
+          cmake --build . --verbose
+          cmake --install . --prefix "${{ env.ROOT_PATH }}/sysroot"
+
+      # TODO: figure out how to cache this for builds where we don't change any source code
+      - name: Build patomic
+        if: env.SKIP_JOB != 'true'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          cd patomic
+          mkdir build
+          cd build
+          cmake --preset ${{ env.CMAKE_PRESET }} -DBUILD_SHARED_LIBS=${{ matrix.cmake_build_shared }} -DCMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} -DGTest_ROOT:PATH="${{ env.ROOT_PATH }}/sysroot" ..
+          cmake --build . --verbose
+
+      - name: Test patomic
+        if: env.SKIP_JOB != 'true'
+        continue-on-error: true
+        env:
+          LABEL_REGEX: ${{ matrix.kind == 'coverage' && '^(ut)$' || '^(.*)$' }}
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          cd patomic/build
+          ctest --label-regex "${{ env.LABEL_REGEX }}" --verbose --output-junit Testing/Temporary/results.xml .
+
+      - name: Prepare Test Results
+        if: env.SKIP_JOB != 'true'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          mkdir -p upload/test/${{ matrix.kind }}
+          python3 patomic/test/improve_ctest_xml.py --input patomic/build/Testing/Temporary/results.xml --triple ${{ matrix.kind }}-${{ env.UNIQUE_NAME }} --output upload/test/${{ matrix.kind }}/${{ env.UNIQUE_NAME }}.xml
+
+      - name: Upload Test Results
+        if: env.SKIP_JOB != 'true'
+        uses: actions/upload-artifact@v3
+        with:
+          name: test-results
+          path: ${{ env.ROOT_PATH }}/upload/test/
+
+      - name: Generate Lcov Tracefile and Root Path File (clang)
+        if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage' && matrix.compiler == 'clang'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          # set up directory
+          mkdir -p upload/cov/${{ env.UNIQUE_NAME }}
+          cd patomic/build
+          
+          # need llvm tools to be installed
+          sudo apt install llvm
+
+          # merge coverage output from all tests
+          # use find because bash on GitHub Actions currently does not support '**'
+          find test/working -type f -name "*.profraw" -print0 | xargs -0 llvm-profdata merge -output=patomic.profdata
+          
+          # convert profdata to lcov tracefile, and copy to upload
+          lib_files=(libpatomic.*)  # matches shared/static lib files and symlinks
+          llvm-cov export -format=lcov -instr-profile=patomic.profdata -object="${lib_files[0]}" >> patomic.lcov
+          cp patomic.lcov ../../upload/cov/${{ env.UNIQUE_NAME }}/patomic.lcov
+          
+          # necessary because publish-coverage expects this file due to workaround for native test coverage
+          cd ../..
+          echo "$PWD" >> upload/cov/${{ env.UNIQUE_NAME }}/patomic.rootpath
+
+      - name: Generate Lcov Tracefile and Root Path File (gcc)
+        if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage' && matrix.compiler == 'gcc'
+        run: |
+          cd ${{ env.ROOT_PATH }}
+          
+          # install lcov
+          sudo apt install lcov
+          
+          # set up directory
+          mkdir -p upload/cov/${{ env.UNIQUE_NAME }}
+          
+          # merge all .gcda files into an lcov tracefile, and copy to upload
+          # all tests have .gcno, but only tests that were executed have .gcda
+          lcov --directory --rc lcov_branch_coverage=1 patomic/build --capture --output-file patomic.lcov
+          cp patomic.lcov upload/cov/${{ env.UNIQUE_NAME }}/patomic.lcov
+          
+          # we need root path file because next job needs it because clang (above) needs it
+          # we might also need it for gcc, but at the moment i have no idea
+          echo "$PWD" >> upload/cov/${{ env.UNIQUE_NAME }}/patomic.rootpath
+
+      - name: Upload Internal Coverage Results
+        if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage'
+        uses: actions/upload-artifact@v3
+        with:
+          name: test-coverage-internal
+          path: ${{ env.ROOT_PATH }}/upload/cov/
\ No newline at end of file
diff --git a/.github/workflows/reusable/test-warning.yml b/.github/workflows/reusable/test-warning.yml
deleted file mode 100644
index d120c4767..000000000
--- a/.github/workflows/reusable/test-warning.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: Run Tests with Warnings
-
-on:
-  workflow_call:
-    inputs:
-      preset:
-        description: 'CMake preset used to build patomic and googletest'
-        required: true
-        type: string
-        default: ''
-      architecture:
-        description: 'CPU architecture tests are run on, passed to QEMU'
-        required: true
-        type: string
-        default: ''
-      triple:
-        description: 'Platform triple to compile for, used to install correct compiler and sysroot'
-        required: true
-        type: string
-        default: ''
-      cmake_build_shared:
-        description: 'Determines value of BUILD_SHARED_LIBS for CMake, must be considered a boolean by CMake'
-        required: true
-        type: string
-        default: ''
-      patomic_path:
-        description: 'Source directory of patomic repository'
-        required: true
-        type: string
-        default: ''
-
-jobs:
-  warning:
-    steps:
-      - name: Check No Empty Inputs
-        shell: bash
-        run: |
-          # checks input is not empty
-            check_input() {
-              input_name="${1}"
-              input_value="${2}"
-              if [[ -z "${input_value}" ]]; then
-                echo "Error: required input '${input_name}' is empty or was not specified."
-                exit 1
-              fi
-            }
-
-            # apply function to all inputs
-            check_input "preset" "${{ inputs.preset }}"
-            check_input "architecture" "${{ inputs.architecture }}"
-            check_input "triple" "${{ inputs.triple }}"
-            check_input "cmake_build_shared" "${{ inputs.cmake_build_shared }}"
-            check_input "patomic_path" "${{ inputs.patomic_path }}"
diff --git a/.github/workflows/test-warning.yml b/.github/workflows/test-warning.yml
deleted file mode 100644
index ec30685fb..000000000
--- a/.github/workflows/test-warning.yml
+++ /dev/null
@@ -1,364 +0,0 @@
-name: Run Tests With Warnings
-
-on:
-  pull_request:
-    branches:
-      - '**'
-
-jobs:
-  test-native:
-    runs-on: ${{ matrix.os }}-latest
-    env:
-      PATOMIC_CI_ID: ${{ matrix.architecture }}-${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.build-shared }}
-    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
-        # build-shared gets converted to cmake option
-        build-shared:
-          - static
-          - shared
-        # convert verbose labels to usable values (which don't appear in action name)
-        # also just other variables that we don't want appearing in action name
-        include:
-          # use Release for everything
-          - cmake-build-type: Release
-          # build-shared -> cmake-build-shared
-          - build-shared: static
-            cmake-build-shared: OFF
-          - build-shared: shared
-            cmake-build-shared: ON
-          # platform -> os, preset, compiler, architecture
-          - platform: windows-gcc-x86_64
-            os: windows
-            preset: patomic-ci-native-unix-gcc-warning
-            compiler: gcc
-            architecture: x86_64
-          - platform: windows-msvc-x86_32
-            os: windows
-            preset: patomic-ci-native-windows-msvc-warning -A Win32
-            compiler: msvc
-            architecture: x86_32
-          - platform: windows-msvc-x86_64
-            os: windows
-            preset: patomic-ci-native-windows-msvc-warning -A x64
-            compiler: msvc
-            architecture: x86_64
-          - platform: macos-clang-x86_64
-            os: macos
-            preset: patomic-ci-native-unix-clang-warning
-            compiler: clang
-            architecture: x86_64
-          - platform: linux-clang-x86_64
-            os: ubuntu
-            preset: patomic-ci-native-unix-clang-warning
-            compiler: clang
-            architecture: x86_64
-          - platform: linux-gcc-x86_64
-            os: ubuntu
-            preset: patomic-ci-native-unix-gcc-warning
-            compiler: gcc
-            architecture: x86_64
-
-    steps:
-      - name: Checkout patomic
-        uses: actions/checkout@v4
-        with:
-          path: patomic
-
-      - name: Try Using Reusable workflow
-        uses: patomic/patomic/.github/workflows/reusable/test-warning.yml@feature/ghi-20-setup-pipeline-and-presets
-        with:
-          test_type: ansi
-          preset: ${{ matrix.preset }}
-          architecture: ${{ matrix.architecture }}
-          triple: ${{ matrix.platform }}
-          cmake_build_shared: ${{ matrix.cmake-build-shared }}
-          patomic_path: patomic
-
-      - name: Restore Cached GoogleTest
-        id: cache-googletest
-        uses: actions/cache@v3
-        with:
-          path: googletest/build/install
-          key: googletest-${{ env.PATOMIC_CI_ID }}
-
-      - name: Checkout GoogleTest
-        if: steps.cache-googletest.outputs.cache-hit != 'true'
-        uses: actions/checkout@v4
-        with:
-          repository: google/googletest
-          path: googletest
-
-      - name: Build and Install GoogleTest
-        if: steps.cache-googletest.outputs.cache-hit != 'true'
-        run: |
-          cd googletest
-          cp ../patomic/CMakePresets.json .
-          mkdir build
-          cd build
-          cmake --preset ${{ matrix.preset }} -DCMAKE_CXX_FLAGS="" -DBUILD_SHARED_LIBS=${{ matrix.cmake-build-shared }} -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -Dgtest_force_shared_crt=ON -Dgtest_hide_internal_symbols=ON ..
-          cmake --build . --verbose --config ${{ matrix.cmake-build-type }}
-          cmake --install . --config ${{ matrix.cmake-build-type }} --prefix install
-
-      - name: Build patomic
-        run: |
-          cd patomic
-          mkdir build
-          cd build
-          cmake --preset ${{ matrix.preset }} -DBUILD_SHARED_LIBS=${{ matrix.cmake-build-shared }} -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -DGTest_ROOT:PATH="../../googletest/build/install/lib/cmake/GTest" ..
-          cmake --build . --verbose --config ${{ matrix.cmake-build-type }}
-
-      - name: Test patomic
-        continue-on-error: true
-        run: |
-          cd patomic/build
-          ctest --verbose --output-junit Testing/Temporary/results.xml --build-config ${{ matrix.cmake-build-type }} .
-
-      - name: Prepare Test Results
-        shell: bash
-        run: |
-          mkdir upload
-          python3 patomic/test/improve_ctest_xml.py --input patomic/build/Testing/Temporary/results.xml --triple ${{ env.PATOMIC_CI_ID }} --output upload/${{ env.PATOMIC_CI_ID }}.xml
-
-      - name: Upload Test Results
-        uses: actions/upload-artifact@v3
-        with:
-          name: test-warning-results
-          path: upload/
-
-  test-qemu:
-    runs-on: ubuntu-latest
-    env:
-      PATOMIC_CI_ID: ${{ matrix.architecture }}-ubuntu-${{ matrix.compiler }}-${{ matrix.build-shared }}
-      PATOMIC_CI_XARCH: ${{ matrix.architecture }}
-      PATOMIC_CI_XTRIPLE: ${{ matrix.triple }}
-      # PATOMIC_CI_XCOMPILER is specified as a cache variable in the appropriate presets (since it's a constant)
-      PATOMIC_CI_XCOMPILER_VERSION: ${{ matrix.compiler-version }}
-      PATOMIC_CI_SYSROOT: ${{ matrix.sysroot }}
-    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
-          - arm
-          - armhf
-          - hppa
-          # 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
-          - s390x
-          # fixme: sh4 is supported (only for gcc), but segfaults qemu when built with gcc (GHI #25)
-          # - sh4
-          - sparc64
-          - x86_32
-        compiler:
-          - clang
-          - gcc
-        # build-shared gets converted to cmake option
-        build-shared:
-          - static
-          - shared
-        # exclude architectures unsupported by certain compilers
-        exclude:
-          - architecture: alpha
-            compiler: clang
-          - architecture: hppa
-            compiler: clang
-          # fixme: x86_32 is supported, but not sure how to make it work with clang (GHI #25)
-          - architecture: x86_32
-            compiler: clang
-          # fixme: riscv64 is supported, but gcc errors on a system header (GHI #25)
-          - architecture: riscv64
-            compiler: gcc
-          - architecture: sh4
-            compiler: clang
-          # fixme: sparc64 is supported, but segfaults qemu when built with gcc (GHI #25)
-          - architecture: sparc64
-            compiler: gcc
-        # convert verbose labels to usable values (which don't appear in action name)
-        # also just other variables that we don't want appearing in action name
-        include:
-          # common local sysroot location for everything
-          # don't prefix with ${HOME} because not always expanded by actions
-          - sysroot: '~/sysroot'
-          # use Release for everything
-          - cmake-build-type: Release
-          # build-shared -> cmake-build-shared
-          - build-shared: static
-            cmake-build-shared: OFF
-          - build-shared: shared
-            cmake-build-shared: ON
-          # toolchain versions to use
-          - gcc-version: 10
-          - llvm-version: 15
-          # compiler -> preset, compiler-version
-          - compiler: clang
-            preset: patomic-ci-qemu-ubuntu-clang-warning
-            compiler-version: 15
-          - compiler: gcc
-            preset: patomic-ci-qemu-ubuntu-gcc-warning
-            compiler-version: 10
-          # architecture -> triple
-          - 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
-
-    steps:
-      - name: Install Toolchains and Qemu
-        run: |
-          sudo apt update
-          sudo apt install g++-${{ matrix.gcc-version }}-multilib
-          sudo apt install g++-${{ matrix.gcc-version }}-${{ matrix.triple }}
-          sudo apt install llvm-${{ matrix.llvm-version }}
-          sudo apt install clang-${{ matrix.llvm-version }}
-          sudo apt install qemu-user
-
-      # - name: Show what architectures clang-${{ matrix.llvm-version }} supports
-      #   run: |
-      #    clang-${{ matrix.llvm-version }} --print-targets
-
-      # - name: Display what was Installed
-      #   run: |
-      #     sudo apt update
-      #     sudo apt-cache dumpavail | sudo dpkg --merge-avail -
-      #     sudo apt install apt-file
-      #     sudo apt install tree
-      #     sudo apt-file update
-      #     sudo apt-file list g++-${{ matrix.gcc-version }}-multilib
-      #     sudo apt-file list g++-${{ matrix.gcc-version }}-${{ matrix.triple }}
-      #     tree /usr/${{ matrix.triple }}
-      #     tree /usr/lib/gcc-cross/${{ matrix.triple }}/${{ matrix.gcc-version}}
-
-      - name: Checkout patomic
-        uses: actions/checkout@v4
-        with:
-          path: patomic
-
-      - name: Restore Cached GoogleTest and Sysroot
-        id: cache-googletest
-        uses: actions/cache@v3
-        with:
-          path: ${{ matrix.sysroot }}
-          # need both gcc and llvm version in case of building with clang but using gcc sysroot
-          key: googletest-${{ env.PATOMIC_CI_ID }}-${{ matrix.gcc-version }}-${{ matrix.llvm-version }}
-
-      - name: Set Up Sysroot
-        if: steps.cache-googletest.outputs.cache-hit != 'true'
-        # cannot use symlink or reference directory directly, will not work
-        run: |
-          cp -r /usr/${{ matrix.triple }}/ ${{ matrix.sysroot }}
-
-      - name: Checkout GoogleTest
-        if: steps.cache-googletest.outputs.cache-hit != 'true'
-        uses: actions/checkout@v4
-        with:
-          repository: google/googletest
-          path: googletest
-
-      - name: Build and Install GoogleTest
-        if: steps.cache-googletest.outputs.cache-hit != 'true'
-        run: |
-          cd googletest
-          cp -r ../patomic/ci/ ./
-          cp ../patomic/CMakePresets.json .
-          mkdir build
-          cd build
-          cmake --preset ${{ matrix.preset }} -DCMAKE_CXX_FLAGS="" -DBUILD_SHARED_LIBS=${{ matrix.cmake-build-shared }} -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -Dgtest_force_shared_crt=ON -Dgtest_hide_internal_symbols=ON ..
-          cmake --build . --verbose
-          cmake --install . --prefix ${{ matrix.sysroot }}
-
-      - name: Build patomic
-        run: |
-          cd patomic
-          mkdir build
-          cd build
-          cmake --preset ${{ matrix.preset }} -DBUILD_SHARED_LIBS=${{ matrix.cmake-build-shared }} -DCMAKE_BUILD_TYPE=${{ matrix.cmake-build-type }} -DGTest_ROOT:PATH="${{ matrix.sysroot }}/lib/cmake/GTest" ..
-          cmake --build . --verbose
-
-      - name: Test patomic
-        continue-on-error: true
-        run: |
-          cd patomic/build
-          ctest --verbose --output-junit Testing/Temporary/results.xml .
-
-      - name: Prepare Test Results
-        run: |
-          mkdir upload
-          python3 patomic/test/improve_ctest_xml.py --input patomic/build/Testing/Temporary/results.xml --triple ${{ env.PATOMIC_CI_ID }} --output upload/${{ env.PATOMIC_CI_ID }}.xml
-
-      - name: Upload Test Results
-        uses: actions/upload-artifact@v3
-        with:
-          name: test-warning-results
-          path: upload/
-
-  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-warning-results
-          path: test-warning-results/
-
-      - name: Publish Test Results
-        uses: EnricoMi/publish-unit-test-result-action@v2
-        with:
-          action_fail: true
-          action_fail_on_inconclusive: true
-          check_name: "Tests Results: Warning"
-          files: test-warning-results/**/*.xml
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 000000000..0ed5beb3a
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,369 @@
+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
+          - arm
+          - armhf
+          - hppa
+          - m68k
+          - mips
+          - mips64
+          - mips64el
+          - mipsel
+          - ppc
+          - ppc64
+          - ppc64le
+          - riscv64
+          - s390x
+          - sh4
+          - sparc64
+          - x86_32
+        # convert architectures to triples
+        include:
+          - architecture: aarch64
+            triple: aarch64-linux-gnu
+          - architecture: alpha
+            triple: alpha-linux-gnu
+            skip_llvm: true  # unsupported
+          - architecture: arm
+            triple: arm-linux-gnueabi
+          - architecture: armhf
+            triple: arm-linux-gnueabihf
+          - architecture: hppa
+            triple: hppa-linux-gnu
+            skip_llvm: true  # unsupported
+          - architecture: m68k
+            triple: m68k-linux-gnu
+            skip_gcc: true  # fixme: segfaults qemu (GHI #25)
+            skip_llvm: true  # fixme: ICEs clang (GHI #25)
+          - 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
+            skip_gcc: true  # fixme: errors on system header (GHI #25)
+          - architecture: s390x
+            triple: s390x-linux-gnu
+          - architecture: sh4
+            triple: sh4-linux-gnu
+            skip_gcc: true  # fixme: segfaults qemu (GHI #25)
+            skip_llvm: true  # unsupported
+          - architecture: sparc64
+            triple: sparc64-linux-gnu
+            skip_gcc: true  # fixme: segfaults qemu (GHI #25)
+          - architecture: x86_32
+            triple: i686-linux-gnu
+            skip_llvm: true  # fixme: supported but not sure how (GHI #25)
+    uses: doodspav/patomic/.github/workflows/reusable-test-qemu.yml@feature/ghi-20-inner-stuff
+    with:
+      triple: ${{ matrix.triple }}
+      architecture: ${{ matrix.architecture }}
+      skip_gcc: ${{ matrix.skip_gcc == true }}
+      skip_llvm: ${{ matrix.skip_llvm == true }}
+
+  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"
+          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 "lcov_branch_coverage = 1" >> ./lcovrc
+          
+          echo "Contents of lcovrc:"
+          cat ./lcovrc
+          sudo cp ./lcovrc /etc/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 --precision 2 "./test-coverage-internal/${arch}/patomic.lcov"
+          done
+          
+          # generate html files for combined coverage
+          mkdir ./test-coverage-internal/universal
+          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" "./test-coverage-internal/universal/patomic.lcov")
+          "${lcov_combine_command[@]}"
+          genhtml --output-directory "./test-coverage/universal" --title "patomic-universal" --show-details --num-spaces 4 --legend --demangle-cpp --precision 2 ./test-coverage-internal/universal/patomic.lcov
+
+      - name: Upload Coverage HTML Results
+        uses: actions/upload-artifact@v3
+        with:
+          name: test-coverage
+          path: test-coverage/
+
+      - name: Parse Lcov Files Into Json
+        shell: python
+        run: |
+          import json, os, pathlib, re, subprocess
+            
+          # constants
+          BASE_DIR = pathlib.Path("./test-coverage-internal")
+          OUT_FILE = pathlib.Path("./patomic.json")
+          
+          # iterate through all non-hidden directories
+          archs = [d for d in next(os.walk(BASE_DIR))[1] if d[0] != '.']
+          json_out = {}
+          for arch in archs:
+            json_out[arch] = {}
+            
+            # get lcov summary
+            cmd = f"lcov --summary {BASE_DIR / arch / 'patomic.lcov'}"
+            lcov_summary = subprocess.run(cmd, capture_output=True, shell=True, check=True).stdout.decode()
+            
+            # go through lcov summary
+            for line in lcov_summary.split('\n'):
+              
+              # skip non-info lines
+              if not line.lstrip().startswith(("lines", "functions", "branches")):
+                continue
+              attr_name = line.lstrip().split('.', maxsplit=1)[0]
+              
+              # parse coverage stats
+              if "no data found" in line:
+                json_out[arch][attr_name] = { "percentage": 100.0, "count": 0, "total": 0 }
+              else:
+                pattern = r"(\d+\.\d+)% \((\d+) of (\d+)"
+                matches = re.findall(pattern, line)[0]
+                json_out[arch][attr_name] = { "percentage": float(matches[0]), "count": int(matches[1]), "total": int(matches[2]) }
+          
+          # write results to json file
+          with open(OUT_FILE, 'w') as f:
+            json.dump(json_out, f, indent=4)
+
+      - name: Create Coverage Summary File
+        id: coverage-summary
+        shell: python
+        run: |
+          import json, os, pathlib
+          
+          # constants
+          HI_LIMIT, MED_LIMIT = ${{ env.HI_LIMIT }}, ${{ env.MED_LIMIT }}
+          IN_FILE = pathlib.Path("./patomic.json")
+          OUT_FILE = pathlib.Path("./patomic.md")
+          
+          # markdown prologue
+          md_string = "## Test Coverage\n"
+          md_string += "| Target | Lines | Functions | Branches |\n"
+          md_string += "| :----- | ----: | --------: | -------: |\n"
+          
+          # keep track of issues
+          any_fail = False
+          universal_warn = False
+          
+          # go through json file
+          with open(IN_FILE, 'r') as fp:
+            j = json.load(fp)
+            
+            # sort archs except for 'universal' first
+            archs = sorted(list(j.keys()), key=lambda a: a if a != 'universal' else '\0')
+            
+            # helper lambdas
+            make_emoji = lambda p: "✅" if p >= HI_LIMIT else ("⚠️" if p >= MED_LIMIT else "🛑")
+            field_2str = lambda f: f"{float(f['percentage']):.2f}% ({f['count']}/{f['total']}) {make_emoji(float(f['percentage']))}"
+            
+            # write summary of each arch to markdown file
+            for arch in archs:
+              line = f"| `{arch}` | `{field_2str(j[arch]['lines'])}` | `{field_2str(j[arch]['functions'])}` | `{field_2str(j[arch]['branches'])}` |\n"
+              md_string += line
+              
+              # check for warnings
+              any_fail = any_fail or "🛑" in line
+              universal_warn = universal_warn or (arch == 'universal' and "⚠️" in line)
+          
+          # markdown epilogue
+          sha = "${{ github.event.pull_request.head.sha }}"
+          md_string += f"\n`✅ >= {HI_LIMIT}`, `⚠️ >= {MED_LIMIT}`, `🛑 < {MED_LIMIT}`\n"
+          md_string += f"\nResults for commit [`{sha[:8]}`](${{ github.server_url }}/${{ github.repository }}/commit/{sha}).\n"
+          
+          # write to file
+          with open(OUT_FILE, 'wb') as fp:
+            fp.write(md_string.encode())
+          
+          # write checks to output
+          with open(os.environ["GITHUB_OUTPUT"], 'a') as fp:
+            print(f"any_fail={int(any_fail)}", file=fp)
+            print(f"universal_warn={int(universal_warn)}", file=fp)
+
+      - name: Publish Coverage in Job Summary
+        run: |
+          echo "$(cat ./patomic.md)" >> $GITHUB_STEP_SUMMARY
+
+      - name: Publish Coverage in Pull Request Comment
+        uses: thollander/actions-comment-pull-request@v2
+        with:
+          comment_tag: coverage-summary
+          filePath: patomic.md
+
+      - name: Fail if Any Coverage Fails or Universal Coverage Warns
+        run: |
+          if [[ '${{ steps.coverage-summary.outputs.any_fail }}' != '0' ]]; then
+            msg="At least one coverage result was below the threshold of ${{ env.MED_LIMIT }}% (a.k.a. 🛑)"
+            echo "::error title=Any Coverage Failure::${msg}"
+            exit 1
+          elif [[ '${{ steps.coverage-summary.outputs.universal_warn }}' != '0' ]]; then
+            msg="Universal coverage result was below the threshold of ${{ env.HI_LIMIT }}% (a.k.a. ⚠️)"
+            echo "::error title=Universal Coverage Warning::${msg}"
+            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
\ No newline at end of file
diff --git a/test/cmake/CreateTest.cmake b/test/cmake/CreateTest.cmake
index 7bbe7f45e..054058c7e 100644
--- a/test/cmake/CreateTest.cmake
+++ b/test/cmake/CreateTest.cmake
@@ -9,6 +9,10 @@
 # - executable name -> ${name} (e.g. SomeExample on Unix or SomeExample.exe on Windows)
 # - install directory -> ${CMAKE_INSTALL_TESTDIR}/${kind} (e.g. share/test/bt)
 #
+# Labels:
+# - each test (not target) will have ${kind} appended to its LABELS property
+# - main use case is for code coverage to be able to only run unit tests
+#
 # Working Directory (CTest):
 # - each test target has its working directory set to ${PROJECT_BINARY_DIR}/working/${kind}/${name}
 # - if a multi-config generator is used, 'working' is replaced with 'working/$<CONFIG>'
@@ -187,6 +191,12 @@ function(_create_test)
         WORKING_DIRECTORY "${test_working_dir}"
     )
 
+    # add label to tests so ctest can run them by kind
+    set_property(
+        TEST "${added_tests}"
+        APPEND PROPERTY LABELS "${kind}"
+    )
+
     # custom target to make sure the working directory exists for the test
     add_custom_target(
         ${target}-create-working-dir