name: tests

on:
  push:
    paths-ignore: ['**.md', '**.svg', '**.png']
  pull_request:
    paths-ignore: ['**.md', '**.svg', '**.png']

jobs:
  linux:
    runs-on: ubuntu-latest
    name: linux-${{ matrix.config.target }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        config:
        - {target: clang,       cc: clang,      }
        - {target: clang-x86,   cc: clang,      flags: -DCMAKE_C_FLAGS="-m32",    install: "gcc-multilib"   }
        - {target: gcc,         cc: gcc,        }
        # Builds without uvwasi
        - {target: gcc-no-uvwasi,   cc: gcc,    flags: -DBUILD_WASI=simple   }
        - {target: clang-no-uvwasi, cc: clang,  flags: -DBUILD_WASI=simple   }
        # Debug builds
        - {target: gcc-debug,               cc: gcc,    flags: -DCMAKE_BUILD_TYPE=Debug                         }
        - {target: clang-no-uvwasi-debug,   cc: clang,  flags: -DCMAKE_BUILD_TYPE=Debug -DBUILD_WASI=simple     }

        # TODO: fails on numeric operations
        #- {target: gcc-x86,     cc: gcc,        flags: "-m32",                    install: "gcc-multilib"   }

    steps:
    - uses: actions/checkout@v4
    - name: Install ${{ matrix.config.install }}
      if: ${{ matrix.config.install }}
      run: |
        sudo apt update
        sudo apt install ${{ matrix.config.install }}
    - name: Configure
      env:
        CC: ${{ matrix.config.cc }}
        CFLAGS: ${{ matrix.config.cflags }}
      run: |
        mkdir build
        cd build
        cmake ${{ matrix.config.flags }} ..
    - name: Build
      run: |
        cmake --build build
    - name: Test WebAssembly spec
      run: cd test && python3 run-spec-test.py
    - name: Test previous WebAssembly specs
      run: |
        cd test
        python3 run-spec-test.py --spec=v1.1
    - name: Test WASI apps
      run: cd test && python3 run-wasi-test.py

  alpine-multiarch:
    runs-on: ubuntu-latest
    name: alpine-${{ matrix.arch }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        arch: [ amd64, s390x, ppc64le, arm64v8 ]

    steps:
    - uses: actions/checkout@v4
    - name: Setup multiarch/qemu-user-static
      if: matrix.arch != 'amd64'
      run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    - name: Run tests
      uses: addnab/docker-run-action@v3
      with:
        image: ${{ matrix.arch }}/alpine:3.19
        options: --volume=${{ github.workspace }}:/work --workdir=/work
        run: |
          set -e
          apk add --no-cache build-base cmake python3 git
          echo "------------ Build Wasm3 ------------"
          mkdir build && cd build && cmake .. && cd ..
          cmake --build build
          echo "------------ Test i32 const ------------"
          echo AGFzbQEAAAAGBgF/AEElCwcLAQdteWNvbnN0AwAACARuYW1lAgEA | base64 -d > myconst.wasm
          echo ':get-global myconst' | ./build/wasm3 --repl myconst.wasm
          echo ':get-global myconst' | ./build/wasm3 --repl myconst.wasm 2>&1 | grep 37
          echo "------------ Run tests ------------"
          cd test && python3 run-spec-test.py && cd ..
          cd test && python3 run-wasi-test.py --fast && cd ..
          echo "-------- All tests finished -------"

  macos:
    runs-on: macos-latest
    name: macos-${{ matrix.config.target }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        config:
        - {target: uvwasi,      }
        - {target: no-uvwasi,   flags: -DBUILD_WASI=simple   }

    steps:
    - uses: actions/checkout@v4
    - name: Configure
      run: |
        mkdir build
        cd build
        cmake ${{ matrix.config.flags }} ..
    - name: Build
      run: |
        cmake --build build
    - name: Test WebAssembly spec
      run: cd test && python3 run-spec-test.py
    - name: Test previous WebAssembly specs
      run: |
        cd test
        python3 run-spec-test.py --spec=v1.1
    - name: Test WASI apps
      run: cd test && python3 run-wasi-test.py

  windows:
    runs-on: windows-latest
    name: windows-${{ matrix.config.target }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        config:
        - {target: clang-x64,   platform: "-A x64",     toolset: "-T ClangCL"   }
        - {target: msvc-x64,    platform: "-A x64",     toolset: ""             }
        - {target: clang-x86,   platform: "-A Win32",   toolset: "-T ClangCL"   }
        - {target: msvc-x86,    platform: "-A Win32",   toolset: ""             }
        # Builds without uvwasi
        - {target: clang-x64-no-uvwasi,   platform: "-A x64",     toolset: "-T ClangCL",  flags: "-DBUILD_WASI=simple"   }
        - {target: msvc-x64-no-uvwasi,    platform: "-A x64",     toolset: "",            flags: "-DBUILD_WASI=simple"   }
        - {target: clang-x86-no-uvwasi,   platform: "-A Win32",   toolset: "-T ClangCL",  flags: "-DBUILD_WASI=simple"   }
        - {target: msvc-x86-no-uvwasi,    platform: "-A Win32",   toolset: "",            flags: "-DBUILD_WASI=simple"   }

    defaults:
      run:
        shell: cmd

    steps:
    - uses: actions/checkout@v4
    - name: Configure
      run: |
        mkdir build
        cd build
        cmake ${{ matrix.config.platform }} ${{ matrix.config.toolset }} ${{ matrix.config.flags }} ..
    - name: Build
      run: |
        cmake --build build --config Release
        cp ./build/Release/wasm3.exe ./build/
    - name: Test WebAssembly spec
      run: |
        cd test
        python run-spec-test.py
    - name: Test previous WebAssembly specs
      run: |
        cd test
        python run-spec-test.py --spec=v1.1
    - name: Test WASI apps
      run: |
        cd test
        python run-wasi-test.py

  cygwin-build:
    runs-on: windows-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@main
      - name: Set up Cygwin
        uses: egor-tensin/setup-cygwin@master
        with:
            platform: x64
            packages: make gcc-g++ cmake
      - run: cd $GITHUB_WORKSPACE && cmake -DBUILD_WASI=simple . && make
        shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}'

  wasi:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Setup environment
      env:
        WASI_VERSION: 11
        WASI_VERSION_FULL: "11.0"
      run: |
        echo "WASI_VERSION=${WASI_VERSION}" >> $GITHUB_ENV
        echo "WASI_VERSION_FULL=${WASI_VERSION_FULL}" >> $GITHUB_ENV
        echo "WASI_SDK_PATH=${GITHUB_WORKSPACE}/.toolchains/wasi-sdk-${WASI_VERSION_FULL}" >> $GITHUB_ENV
    - name: Install WASI SDK
      run: |
        mkdir -p .toolchains
        cd .toolchains
        WASI_TAR=wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz
        wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/${WASI_TAR}
        tar xvf ${WASI_TAR}
    - name: Install Wasmer
      run: curl https://get.wasmer.io -sSfL | sh -s "1.0.2"
    - name: Configure
      run: |
        mkdir build-wasi
        cd build-wasi
        cmake -DCMAKE_TOOLCHAIN_FILE="${WASI_SDK_PATH}/share/cmake/wasi-sdk.cmake" -DWASI_SDK_PREFIX="${WASI_SDK_PATH}" ..
    - name: Build
      run: |
        cmake --build build-wasi
        cp ./build-wasi/wasm3.wasm ./test/

    - name: Test WebAssembly spec (in Wasmer)
      run: |
        source $HOME/.wasmer/wasmer.sh
        cd test
        python3 run-spec-test.py --exec "wasmer run --mapdir=/:. wasm3.wasm -- --repl"
    - name: Test WASI apps (in Wasmer)
      run: |
        source $HOME/.wasmer/wasmer.sh
        cd test
        python3 run-wasi-test.py --exec "wasmer run --mapdir=/:. wasm3.wasm --" --fast

    - name: Configure (native)
      run: |
        mkdir build
        cd build
        cmake ..
    - name: Build (native)
      run: |
        cmake --build build
    - name: Test WebAssembly spec (in Wasm3, self-hosting)
      run: |
        cd test
        python3 run-spec-test.py --exec "../build/wasm3 --stack-size 2097152 wasm3.wasm --repl"
    - name: Test WASI apps (in Wasm3, self-hosting)
      run: |
        cd test
        python3 run-wasi-test.py --fast --exec "../build/wasm3 --stack-size 2097152 wasm3.wasm"

  ios:
    runs-on: macos-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: List Xcode versions
      run: ls /Applications | grep Xcode
    - name: Select Xcode 14
      run: sudo xcode-select -switch /Applications/Xcode_14.2.app
    - name: Build (iPhone 11)
      run: |
        cd platforms/ios
        xcodebuild build -scheme wasm3 -project wasm3.xcodeproj -configuration Release -destination 'platform=iOS Simulator,name=iPhone 11,OS=16.2'

  android:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - uses: seanmiddleditch/gha-setup-ninja@master
    - name: Set up JDK 11
      uses: actions/setup-java@v4
      with:
        distribution: 'zulu'
        java-version: 11
    - name: Build
      run: |
        cd platforms/android
        ./gradlew build

  python:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - name: Checkout
      uses: actions/checkout@v4
      with:
        path: wasm3
    - name: Checkout pywasm3
      uses: actions/checkout@v4
      with:
        repository: wasm3/pywasm3
        path: pywasm3
    - name: Set up Python
      uses: actions/setup-python@v5
    - name: Update and Build Python module
      run: |
        rm -rf ./pywasm3/wasm3
        cp -r wasm3/source ./pywasm3/wasm3
        pip install ./pywasm3
    - name: Install WABT
      run: |
        sudo apt update
        sudo apt install wabt
    - name: Test
      run: |
        pip install pytest
        cd ./pywasm3
        pytest

  cosmopolitan:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Build αcτµαlly pδrταblε εxεcµταblε
      run: |
        cd platforms/cosmopolitan
        gcc -v
        ./build.sh
    - name: Prepare tests
      run: |
        cd test
        cp ../platforms/cosmopolitan/wasm3.com ./wasm3-lin.com
        cp ../platforms/cosmopolitan/wasm3.com ./wasm3-win.com
        sudo sh -c "echo ':APE:M::MZqFpD::/bin/sh:' >/proc/sys/fs/binfmt_misc/register"
    - name: Test WebAssembly spec
      run: |
        cd test
        python3 run-spec-test.py --exec "./wasm3-lin.com --repl"
    - name: Test WASI apps
      run: |
        cd test
        python3 run-wasi-test.py --fast --exec "./wasm3-lin.com"
    - name: Install Wine64
      run: |
        sudo apt update
        sudo apt install wine64
        wine --version
    - name: Test WebAssembly spec (in Wine)
      run: |
        cd test
        python3 run-spec-test.py --exec "wine ./wasm3-win.com --repl"
    - name: Test WASI apps (in Wine)
      run: |
        cd test
        python3 run-wasi-test.py --fast --exec "wine ./wasm3-win.com"

  cross-qemu:
    runs-on: ubuntu-latest
    name: cross-qemu-${{ matrix.config.target }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        config:
        #- {target: i386,    toolchain: gcc-multilib,              cc: clang -m32,                 qemu: qemu-i386-static    }
        - {target: arm,     toolchain: gcc-arm-linux-gnueabi,     cc: arm-linux-gnueabi-gcc,      qemu: qemu-arm-static     }
        - {target: armhf,   toolchain: gcc-arm-linux-gnueabihf,   cc: arm-linux-gnueabihf-gcc,    qemu: qemu-arm-static     }
        - {target: aarch64, toolchain: gcc-aarch64-linux-gnu,     cc: aarch64-linux-gnu-gcc,      qemu: qemu-aarch64-static }
        - {target: riscv64, toolchain: gcc-riscv64-linux-gnu,     cc: riscv64-linux-gnu-gcc,      qemu: qemu-riscv64-static }
        - {target: ppc,     toolchain: gcc-powerpc-linux-gnu,     cc: powerpc-linux-gnu-gcc,      qemu: qemu-ppc-static     }
        - {target: ppc64,   toolchain: gcc-powerpc64-linux-gnu,   cc: powerpc64-linux-gnu-gcc,    qemu: qemu-ppc64-static   }
        #- {target: ppc64le, toolchain: gcc-powerpc64le-linux-gnu, cc: powerpc64le-linux-gnu-gcc,  qemu: qemu-ppc64le-static }
        - {target: s390x,   toolchain: gcc-s390x-linux-gnu,       cc: s390x-linux-gnu-gcc,        qemu: qemu-s390x-static   }
        - {target: mips,    toolchain: gcc-mips-linux-gnu,          cc: mips-linux-gnu-gcc,         qemu: qemu-mips-static     }
        - {target: mips64,  toolchain: gcc-mips64-linux-gnuabi64,   cc: mips64-linux-gnuabi64-gcc,  qemu: qemu-mips64-static   }
        - {target: mipsel,  toolchain: gcc-mipsel-linux-gnu,        cc: mipsel-linux-gnu-gcc,       qemu: qemu-mipsel-static   }
        - {target: mips64el,toolchain: gcc-mips64el-linux-gnuabi64, cc: mips64el-linux-gnuabi64-gcc,qemu: qemu-mips64el-static }
        - {target: alpha,   toolchain: gcc-alpha-linux-gnu,       cc: alpha-linux-gnu-gcc,        qemu: qemu-alpha-static   }
        # TODO: - {target: sparc64, toolchain: gcc-sparc64-linux-gnu,     cc: sparc64-linux-gnu-gcc,      qemu: qemu-sparc64-static, skip_wasi: true }

        #- {target: i386 (u64 slots),    toolchain: gcc-multilib,              cc: clang -m32,                 qemu: qemu-i386-static,     cflags: -Dd_m3Use32BitSlots=0  }
        - {target: arm (u64 slots),     toolchain: gcc-arm-linux-gnueabi,     cc: arm-linux-gnueabi-gcc,      qemu: qemu-arm-static,      cflags: -Dd_m3Use32BitSlots=0  }
        - {target: aarch64 (u64 slots), toolchain: gcc-aarch64-linux-gnu,     cc: aarch64-linux-gnu-gcc,      qemu: qemu-aarch64-static,  cflags: -Dd_m3Use32BitSlots=0  }
        - {target: ppc (u64 slots),     toolchain: gcc-powerpc-linux-gnu,     cc: powerpc-linux-gnu-gcc,      qemu: qemu-ppc-static,      cflags: -Dd_m3Use32BitSlots=0  }
        - {target: ppc64 (u64 slots),   toolchain: gcc-powerpc64-linux-gnu,   cc: powerpc64-linux-gnu-gcc,    qemu: qemu-ppc64-static,    cflags: -Dd_m3Use32BitSlots=0  }

    steps:
    - uses: actions/checkout@v4
    - name: Install QEMU
      run: |
        sudo apt update
        sudo apt install qemu-user-static
    - name: Install ${{ matrix.config.toolchain }}
      run: |
        sudo apt install ${{ matrix.config.toolchain }}
    - name: Build
      run: |
        mkdir build
        cd build
        ${{ matrix.config.cc }} -DASSERTS -Dd_m3HasWASI ${{ matrix.config.cflags }} \
            -I../source ../source/*.c ../platforms/app/main.c \
            -O3 -g0 -flto -lm -static \
            -o wasm3
    - name: Test WebAssembly spec
      run: |
        cd test
        python3 run-spec-test.py --exec "${{ matrix.config.qemu }} ../build/wasm3 --repl"
    - name: Test WASI apps
      if: ${{ !matrix.config.skip_wasi }}
      run: |
        cd test
        python3 run-wasi-test.py --fast --exec "${{ matrix.config.qemu }} ../build/wasm3"

  platformio:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install PlatformIO
      run: |
        python -m pip install --upgrade pip
        pip install --upgrade platformio
    - name: Build AVR ATmega1284
      run: |
        cd platforms/embedded/arduino
        pio run -e mega1284
        ! nm .pio/build/mega1284/firmware.elf | grep printf
    - name: Build ESP8266
      run: |
        cd platforms/embedded/esp8266
        pio run
    # TODO:
    #- name: Build ESP32
    #  run: |
    #    cd platforms/embedded/esp32-pio
    #    pio run

  platformio-arm:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install PlatformIO
      run: |
        python -m pip install --upgrade pip
        pip install -U platformio
    - name: Build Arduino MKR1000
      run: |
        cd platforms/embedded/arduino
        pio run -e mkr1000
    # TODO:
    #- name: Build Blue Pill (JeeH)
    #  run: |
    #    cd platforms/embedded/bluepill
    #    pio run
    - name: Build TinyBLE
      run: |
        cd platforms/embedded/arduino
        pio run -e tinyBLE
    - name: Build MXChip AZ3166
      run: |
        cd platforms/embedded/arduino
        pio run -e az3166

  platformio-riscv:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install PlatformIO
      run: |
        python -m pip install --upgrade pip
        pip install -U platformio
    - name: Build HiFive1
      run: |
        cd platforms/embedded/hifive1
        pio run
    - name: Build Sipeed MAIX
      run: |
        cd platforms/embedded/arduino
        pio run -e maix

  particle:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    if: "github.event_name == 'push'"

    steps:
    - uses: actions/checkout@v4
    - name: Set up Particle CLI
      run: sudo npm install -g particle-cli
    - name: Log in
      env:
        PARTICLE_TOKEN: ${{ secrets.PARTICLE_TOKEN }}
      run: particle login --token $PARTICLE_TOKEN
    - name: Build Photon
      run: |
        cd platforms/embedded/particle
        particle compile --followSymlinks photon
        particle compile --followSymlinks argon

  esp32-idf:
    runs-on: ubuntu-20.04
    container: igrr/idf-qemu:release-v4.0-esp-develop-20191228
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v2
    - name: Build for ESP32 (IDF v4.0)
      run: |
        . $IDF_PATH/export.sh
        cd platforms/embedded/esp32-idf
        export EXTRA_CFLAGS="-Werror"
        idf.py build
      shell: bash
    - name: Test for ESP32 in QEMU
      run: |
        cd platforms/embedded/esp32-idf
        make-flash-img.sh wasm3 flash_img.bin
        qemu-system-xtensa -machine esp32 -nographic -drive file=flash_img.bin,if=mtd,format=raw -no-reboot | tee out.txt
        grep "Result: 46368" out.txt
        grep "Elapsed: " out.txt
        grep "Restarting..." out.txt
        test $(($(grep "ets Jun  8 2016" out.txt | wc -l))) -eq 1
    - name: Check that IDF and PIO examples are in sync
      run: |
        diff -q platforms/embedded/esp32-idf/main/main.cpp platforms/embedded/esp32-pio/src/main.cpp
      # TODO: also check that the build flags are in sync

  cpp:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v4
      - name: Configure
        run: |
          cd platforms/cpp
          mkdir build
          cd build
          cmake ..
      - name: Build
        run: |
          cd platforms/cpp
          cmake --build build
      - name: Run
        run: |
          cd platforms/cpp/build
          ./wasm3_cpp_example

  as-cpp:
    name: maintenance (build as C++)
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Build
      run: |
        mkdir build
        cd build
        clang -xc++ -Dd_m3HasWASI \
            -I../source ../source/*.c ../platforms/app/main.c \
            -O3 -g0 -lm \
            -o wasm3
    - name: Test
      run: ./build/wasm3 ./test/wasi/simple/test.wasm

  with-logs:
    name: maintenance (debug logs)
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Build
      run: |
        mkdir build
        cd build
        clang -xc++ -Dd_m3HasWASI -DDEBUG \
            -Dd_m3EnableOpTracing=1     \
            -Dd_m3EnableStrace=1        \
            -Dd_m3LogParse=1            \
            -Dd_m3LogModule=1           \
            -Dd_m3LogCompile=1          \
            -Dd_m3LogWasmStack=1        \
            -Dd_m3LogEmit=1             \
            -Dd_m3LogCodePages=1        \
            -Dd_m3LogRuntime=1          \
            -Dd_m3LogNativeStack=1      \
            -Dd_m3RecordBacktraces=1    \
            -I../source ../source/*.c ../platforms/app/main.c \
            -O3 -g0 -lm \
            -o wasm3
    - name: Test
      run: ./build/wasm3 ./test/wasi/simple/test.wasm > /dev/null

  preprocessed-ops:
    name: maintenance (preprocess ops)
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
    - uses: actions/checkout@v4
    - name: Install sponge
      run: |
        sudo apt update
        sudo apt install moreutils
    - name: Build
      run: |
        make -f extra/utils.mk preprocess
        mkdir build
        cd build
        gcc -Dd_m3HasWASI \
            -I../source ../source/*.c ../platforms/app/main.c \
            -O3 -g0 -lm \
            -o wasm3
    - name: Test
      run: ./build/wasm3 ./test/wasi/simple/test.wasm

  spellcheck:
    runs-on: ubuntu-latest
    steps:
    - name: Set up Python
      uses: actions/setup-python@v5
    - name: Install codespell
      run: |
        pip install codespell
    - name: Spellcheck
      run: |
        codespell
