name: test
on:
  # run tests on push to master, but not when other branches are pushed to
  push:
    branches:
      - master

  # run tests for all pull requests
  pull_request:
  merge_group:

permissions:
  contents: read

env:
  latest_go: "1.26.x"
  GO111MODULE: on

jobs:
  test:
    strategy:
      matrix:
        # list of jobs to run:
        include:
          - job_name: Windows
            go: 1.26.x
            os: windows-latest

          - job_name: macOS
            go: 1.26.x
            os: macOS-latest
            test_fuse: false

          - job_name: Linux
            go: 1.26.x
            os: ubuntu-latest
            test_cloud_backends: true
            test_fuse: true
            check_changelog: true

          - job_name: Linux (race)
            go: 1.26.x
            os: ubuntu-latest
            test_fuse: true
            test_opts: "-race"

          - job_name: Linux
            go: 1.25.x
            os: ubuntu-latest
            test_fuse: true

    name: ${{ matrix.job_name }} Go ${{ matrix.go }}
    runs-on: ${{ matrix.os }}

    env:
      GOPROXY: https://proxy.golang.org

    steps:
      - name: Check out code
        uses: actions/checkout@v6

      - name: Set up Go ${{ matrix.go }}
        uses: actions/setup-go@v6
        with:
          go-version: ${{ matrix.go }}

      - name: Get programs (Linux/macOS)
        run: |
          echo "build Go tools"
          go install github.com/restic/rest-server/cmd/rest-server@master

          echo "install minio server"
          mkdir $HOME/bin
          if [ "$RUNNER_OS" == "macOS" ]; then
            wget --no-verbose -O $HOME/bin/minio https://dl.minio.io/server/minio/release/darwin-amd64/minio
          else
            wget --no-verbose -O $HOME/bin/minio https://dl.minio.io/server/minio/release/linux-amd64/minio
          fi
          chmod 755 $HOME/bin/minio

          echo "install rclone"
          if [ "$RUNNER_OS" == "macOS" ]; then
            wget --no-verbose -O rclone.zip https://downloads.rclone.org/rclone-current-osx-amd64.zip
          else
            wget --no-verbose -O rclone.zip https://downloads.rclone.org/rclone-current-linux-amd64.zip
          fi
          unzip rclone.zip
          cp rclone*/rclone $HOME/bin
          chmod 755 $HOME/bin/rclone
          rm -rf rclone*

          # add $HOME/bin to path ($GOBIN was already added to the path by setup-go@v3)
          echo $HOME/bin >> $GITHUB_PATH
        if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'

      - name: Get programs (Windows)
        shell: powershell
        run: |
          $ProgressPreference = 'SilentlyContinue'

          echo "build Go tools"
          go install github.com/restic/rest-server/cmd/rest-server@master

          echo "install minio server"
          mkdir $Env:USERPROFILE/bin
          Invoke-WebRequest https://dl.minio.io/server/minio/release/windows-amd64/minio.exe -OutFile $Env:USERPROFILE/bin/minio.exe

          echo "install rclone"
          Invoke-WebRequest https://downloads.rclone.org/rclone-current-windows-amd64.zip -OutFile rclone.zip

          unzip rclone.zip
          copy rclone*/rclone.exe $Env:USERPROFILE/bin

          # add $USERPROFILE/bin to path ($GOBIN was already added to the path by setup-go@v3)
          echo $Env:USERPROFILE\bin >> $Env:GITHUB_PATH

          echo "install tar"
          cd $env:USERPROFILE
          mkdir tar
          cd tar

          # install exactly these versions of tar and the libraries, other combinations might not work!

          Invoke-WebRequest https://github.com/restic/test-assets/raw/master/tar-1.13-1-bin.zip -OutFile tar.zip
          unzip tar.zip
          Invoke-WebRequest https://github.com/restic/test-assets/raw/master/libintl-0.11.5-2-bin.zip -OutFile libintl.zip
          unzip libintl.zip
          Invoke-WebRequest https://github.com/restic/test-assets/raw/master/libiconv-1.8-1-bin.zip -OutFile libiconv.zip
          unzip libiconv.zip

          # add $USERPROFILE/tar/bin to path
          echo $Env:USERPROFILE\tar\bin >> $Env:GITHUB_PATH
        if: matrix.os == 'windows-latest'

      - name: Build with build.go
        run: |
          go run build.go

      - name: Minimal test
        timeout-minutes: 3
        run: |
          ./restic init
          ./restic backup .
        env:
          RESTIC_REPOSITORY: ../testrepo
          RESTIC_PASSWORD: password
          RESTIC_DEBUG_STACKTRACE_SIGINT: "true"

      - name: Run local Tests
        env:
          RESTIC_TEST_FUSE: ${{ matrix.test_fuse }}
        run: |
          go test -cover ${{matrix.test_opts}} ./...

      - name: Test cloud backends
        env:
          RESTIC_TEST_S3_KEY: ${{ secrets.RESTIC_TEST_S3_KEY }}
          RESTIC_TEST_S3_SECRET: ${{ secrets.RESTIC_TEST_S3_SECRET }}
          RESTIC_TEST_S3_REPOSITORY: ${{ secrets.RESTIC_TEST_S3_REPOSITORY }}
          RESTIC_TEST_AZURE_ACCOUNT_NAME: ${{ secrets.RESTIC_TEST_AZURE_ACCOUNT_NAME }}
          RESTIC_TEST_AZURE_ACCOUNT_KEY: ${{ secrets.RESTIC_TEST_AZURE_ACCOUNT_KEY }}
          RESTIC_TEST_AZURE_REPOSITORY: ${{ secrets.RESTIC_TEST_AZURE_REPOSITORY }}
          RESTIC_TEST_B2_ACCOUNT_ID: ${{ secrets.RESTIC_TEST_B2_ACCOUNT_ID }}
          RESTIC_TEST_B2_ACCOUNT_KEY: ${{ secrets.RESTIC_TEST_B2_ACCOUNT_KEY }}
          RESTIC_TEST_B2_REPOSITORY: ${{ secrets.RESTIC_TEST_B2_REPOSITORY }}
          RESTIC_TEST_GS_REPOSITORY: ${{ secrets.RESTIC_TEST_GS_REPOSITORY }}
          RESTIC_TEST_GS_PROJECT_ID: ${{ secrets.RESTIC_TEST_GS_PROJECT_ID }}
          GOOGLE_PROJECT_ID: ${{ secrets.RESTIC_TEST_GS_PROJECT_ID }}
          RESTIC_TEST_GS_APPLICATION_CREDENTIALS_B64: ${{ secrets.RESTIC_TEST_GS_APPLICATION_CREDENTIALS_B64 }}
          RESTIC_TEST_OS_AUTH_URL: ${{ secrets.RESTIC_TEST_OS_AUTH_URL }}
          RESTIC_TEST_OS_TENANT_NAME: ${{ secrets.RESTIC_TEST_OS_TENANT_NAME }}
          RESTIC_TEST_OS_USERNAME: ${{ secrets.RESTIC_TEST_OS_USERNAME }}
          RESTIC_TEST_OS_PASSWORD: ${{ secrets.RESTIC_TEST_OS_PASSWORD }}
          RESTIC_TEST_OS_REGION_NAME: ${{ secrets.RESTIC_TEST_OS_REGION_NAME }}
          RESTIC_TEST_SWIFT: ${{ secrets.RESTIC_TEST_SWIFT }}
          # fail if any of the following tests cannot be run
          RESTIC_TEST_DISALLOW_SKIP: "restic/backend/rest.TestBackendREST,\
            restic/backend/sftp.TestBackendSFTP,\
            restic/backend/s3.TestBackendMinio,\
            restic/backend/rclone.TestBackendRclone,\
            restic/backend/s3.TestBackendS3,\
            restic/backend/swift.TestBackendSwift,\
            restic/backend/b2.TestBackendB2,\
            restic/backend/gs.TestBackendGS,\
            restic/backend/azure.TestBackendAzure"
        run: |
          # prepare credentials for Google Cloud Storage tests in a temp file
          export GOOGLE_APPLICATION_CREDENTIALS=$(mktemp --tmpdir restic-gcs-auth-XXXXXXX)
          echo $RESTIC_TEST_GS_APPLICATION_CREDENTIALS_B64 | base64 -d > $GOOGLE_APPLICATION_CREDENTIALS
          go test -cover -parallel 5 -timeout 15m ./internal/backend/...

        # only run cloud backend tests for pull requests from and pushes to our
        # own repo, otherwise the secrets are not available
        # Skip for Dependabot pull requests as these are run without secrets
        # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#responding-to-events
        if: ((github.repository == 'restic/restic' && github.event_name == 'push') || github.event.pull_request.head.repo.full_name == github.repository) && (github.actor != 'dependabot[bot]') && matrix.test_cloud_backends

      - name: Check changelog files with calens
        run: |
          echo "install calens"
          go install github.com/restic/calens@latest

          echo "check changelog files"
          calens
        if: matrix.check_changelog

  cross_compile:
    strategy:
      matrix:
        # run cross-compile in three batches parallel so the overall tests run faster
        subset:
          - "0/3"
          - "1/3"
          - "2/3"

    env:
      GOPROXY: https://proxy.golang.org

    runs-on: ubuntu-latest

    name: Cross Compile for subset ${{ matrix.subset }}

    steps:
      - name: Check out code
        uses: actions/checkout@v6

      - name: Set up Go ${{ env.latest_go }}
        uses: actions/setup-go@v6
        with:
          go-version: ${{ env.latest_go }}

      - name: Cross-compile for subset ${{ matrix.subset }}
        run: |
          mkdir build-output build-output-debug
          go run ./helpers/build-release-binaries/main.go -o build-output -s . --platform-subset ${{ matrix.subset }}
          go run ./helpers/build-release-binaries/main.go -o build-output-debug -s . --platform-subset ${{ matrix.subset }} --tags debug

  lint:
    name: lint
    runs-on: ubuntu-latest
    permissions:
      contents: read
      # allow annotating code in the PR
      checks: write
    steps:
      - name: Check out code
        uses: actions/checkout@v6

      - name: Set up Go ${{ env.latest_go }}
        uses: actions/setup-go@v6
        with:
          go-version: ${{ env.latest_go }}

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v9
        with:
          # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
          version: v2.12
          args: --verbose --timeout 5m

        # only run golangci-lint for pull requests, otherwise ALL hints get
        # reported. We need to slowly address all issues until we can enable
        # linting the master branch :)
        if: github.event_name == 'pull_request'

      - name: Check go.mod/go.sum
        run: |
          echo "check if go.mod and go.sum are up to date"
          go mod tidy
          git diff --exit-code go.mod go.sum

  analyze:
    name: Analyze results
    needs: [test, cross_compile, lint]
    if: always()

    permissions: # no need to access code
      contents: none

    runs-on: ubuntu-latest
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe
        with:
          jobs: ${{ toJSON(needs) }}

  docker:
    name: docker
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v6

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v6
        with:
          # list of Docker images to use as base name for tags
          images: |
            restic/restic
          # generate Docker tags based on the following events/attributes
          tags: |
            type=schedule
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v7
        with:
          push: false
          context: .
          file: docker/Dockerfile
          pull: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
