# Copyright (C) Markus Franz Xaver Johannes Oberhumer
# RT RunTime checks

# runs ASAN, MSAN, qemu and valgrind checkers; slow!

name: 'Weekly CI RT - ASAN MSAN Valgrind'
on:
  schedule: [cron: '00 3 * * 3'] # run weekly Wednesday 03:00 UTC
  workflow_dispatch:
env:
  CMAKE_REQUIRED_QUIET: OFF
  DEBIAN_FRONTEND: noninteractive

jobs:
  job-runtime-checkers: # uses cmake + make
    if: github.repository_owner == 'upx'
    strategy:
      fail-fast: false
      matrix:
        include:
          - container: 'alpine:3.18'
            release: debug
            qemu: 'qemu-x86_64 -cpu Westmere'
          - container: 'alpine:3.18'
            release: release
            qemu: 'qemu-x86_64 -cpu Westmere'
          - container: 'alpine:edge'
            release: release
            qemu: 'qemu-x86_64 -cpu Westmere'
          - container: 'i386/alpine:edge'
            release: release
            qemu: 'qemu-i386'
    name: ${{ format('{0} {1}', matrix.container, matrix.release) }}
    runs-on: ubuntu-latest
    container: ${{ matrix.container }}
    env:
      release: ${{ matrix.release }}
    steps:
      - name: ${{ format('Install packages {0} {1}', matrix.container, matrix.release) }}
        run: |
          apk update && apk upgrade
          apk add bash clang cmake compiler-rt coreutils g++ git make qemu-i386 qemu-x86_64 tar valgrind
          # this seems to be needed when running in a container (beause of UID mismatch??)
          git config --global --add safe.directory '*'
      - name: ${{ format('Check out UPX {0} source code', github.ref_name) }}
        run: |
          git clone --branch "$GITHUB_REF_NAME" --depth 1 https://github.com/upx/upx .
          git submodule update --init
          git clone --depth=1 https://github.com/upx/upx-testsuite ../upx-testsuite

      - name: 'Build clang-static'
        run: |
          export CC="clang -static" CXX="clang++ -static"
          make UPX_XTARGET=clang-static xtarget/$release
      - name: 'Build clang-asan'
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: ASAN not supported
        run: |
          # unfortunately ASAN does not support static linking
          flags="-fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined -fno-omit-frame-pointer"
          export CC="clang $flags" CXX="clang++ $flags"
          make UPX_XTARGET=clang-asan xtarget/$release
      - name: 'Build clang-msan'
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: MSAN not supported
        run: |
          # unfortunately MSAN does not support static linking
          flags="-fsanitize=memory -fsanitize=undefined -fno-omit-frame-pointer -DDOCTEST_CONFIG_DISABLE=1"
          export CC="clang $flags" CXX="clang++ $flags"
          make UPX_XTARGET=clang-msan xtarget/$release

      - name: 'Make artifact'
        run: |
          N=$(echo "upx-${GITHUB_REF_NAME}-${GITHUB_SHA:0:7}-weekly-ci-runtime-checkers-${{ matrix.container }}-${{ matrix.release }}" | sed 's/[^0-9a-zA-Z_.-]/-/g')
          mkdir -p "tmp/artifact/$N"
          (cd build && cp -ai --parents */*/*/upx "../tmp/artifact/$N")
          (cd tmp/artifact && tar --sort=name -czf "$N.tar.gz" "$N" && rm -rf "./$N")
          # GitHub Actions magic: set "artifact_name" environment value for use in next step
          echo "artifact_name=$N" >> $GITHUB_ENV
      - name: ${{ format('Upload artifact {0}', env.artifact_name) }}
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: missing nodejs on host
        uses: actions/upload-artifact@v3
        with:
          name: ${{ env.artifact_name }}
          path: tmp/artifact

      - name: 'Run basic tests clang-static'
        run: 'make -C build/xtarget/clang-static/$release test'
      - name: 'Run basic tests clang-asan'
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: ASAN not supported
        run: 'make -C build/xtarget/clang-asan/$release test'
      - name: 'Run basic tests clang-msan'
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: MSAN not supported
        run: 'make -C build/xtarget/clang-msan/$release test'

      - name: 'Run testsuite clang-asan'
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: ASAN not supported
        run: |
          export ASAN_OPTIONS="detect_invalid_pointer_pairs=2"
          env -C build/xtarget/clang-asan/$release "$PWD"/misc/testsuite/upx_testsuite_1.sh
      - name: 'Run testsuite clang-msan'
        if: ${{ !startsWith(matrix.container, 'i386/') }} # i386: MSAN not supported
        run: |
          env -C build/xtarget/clang-msan/$release "$PWD"/misc/testsuite/upx_testsuite_1.sh
      - name: 'Run testsuite clang-static - QEMU'
        if: ${{ matrix.qemu }}
        run: |
          export upx_exe_runner="${{ matrix.qemu }}"
          env -C build/xtarget/clang-static/$release "$PWD"/misc/testsuite/upx_testsuite_1.sh
      - name: 'Run testsuite clang-static - Valgrind'
        if: true # very slow
        run: |
          export upx_exe_runner="valgrind --error-exitcode=1 --quiet"
          # on current GitHub CI, takes about 30 minutes for release and 80 minutes for debug builds
          # reduce time for debug builds to about 30 minutes
          test "$release" = "debug" && export UPX_TESTSUITE_LEVEL=4
          env -C build/xtarget/clang-static/$release "$PWD"/misc/testsuite/upx_testsuite_1.sh
