GitHub actionsでCI環境を構築した話（SQLiteFDWを例に）

[SQLite FDW](https://github.com/pgspider/sqlite_fdw/)というPostgreSQL&reg;の拡張機能をGitHub&reg;で公開していますが、
ありがたいことに、機能開発を行ってプルリクエストしてくださる方が出てきました。  
そのコードに対して自動でリグレッションテストを行うために、GitHub上にSQLite FDWのCI環境を構築したので紹介したいと思います。

## CI環境の要件

1. プルリクエストのコードに対して、マージする前にテストを実施すること
1. ジョブ間でデータを受け渡しすること
    - テストで使用するPostgreSQLバージョンは、SQLite FDWのリポジトリ上に情報があります。CI環境にその情報を埋め込まず、自動的に取得するようにします。それを実現するために、バージョン取得ジョブとテスト実行ジョブという2つのジョブを構成したとき、それらのジョブ間でデータ（バージョン情報）の受け渡しを行います。
1. ひとつのジョブ定義に対して、複数のパターンで（パラメータを変えて）を複数のジョブを実行すること
    - SQLite FDWは5つのバージョンのPostgreSQLをサポートします。PostgreSQLバージョン（これがパラメータとなる）を変えて、同一のジョブ定義を使ってテストを実施します。
    - SQLite FDWはデフォルトでは無効化される機能があります。デフォルトと機能を有効化した2種類のパターンで同一のジョブ定義を使ってテストを実施します。
1. CI実行環境上のファイルをあとから参照できるようにすること
    - テストでエラーになった場合、調査のために実行時のログを確認できるようにしたいです。テスト実行により作成されるファイルをジョブ終了後でも読めるようにしておきます。

イメージ（プルリクエストの画面）
![CI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581362/ec35b650-8826-77d3-3ab9-8e7f98e26831.png)

## GitHub actions
GitHub actionsというGitHub上の機能を使うと、一連の処理を自動化できます。

自動化したい処理内容や設定をymlファイル（今回は`CI.yml`としました）に記載し、リポジトリ上の`.github/workflows/`というディレクトリに置きます。

## 処理の実行タイミングと対象

プルリクエスト時に実施するために（要件1）、`on.pull_request`を設定しました。
手動での実行（`on.workflow_dispatch`）、および特定ブランチへのプッシュ時にもテストを実施するための設定（`on.push`）もしておきました。

```yml
on:
  workflow_dispatch:
  pull_request:  
  push:
    branches:
      - master
      - main
```

## 対象のPostgreSQLバージョンの取得

SQLite FDWでは、サポート対象のPostgreSQLバージョンの情報が元々リポジトリ上に保持されています。サポート対象に変更があるたびにCI環境側の設定も変更が必要になってしまうと面倒です。
そこで、CIでは自動的にPostgreSQLのバージョンを取得するようにしました（要件2）。

それを行うジョブ`detect-pgversion`を以下のように定義しました。
```yml
jobs:
  detect-pgversion:
    runs-on: ubuntu-22.04
    outputs:
      pgversion: ${{ steps.detect-pgversion.outputs.targets }}
    steps:
      - uses: actions/checkout@v4
      
      - name: detect-pgversion
        id: detect-pgversion
        run: |
          targets=`bash GitHubActions/detect_targets.sh`
          echo "targets=$targets" >> $GITHUB_OUTPUT
```
PodstgreSQLバージョンを取得するスクリプトを`GitHubActions/detect_targets.sh`に置きました。
このスクリプトを実行すると、カンマ区切りで対象バージョンが返るようになっています（"16.0","15.4","14.9","13.12","12.16"）。  
`変数名=値`の形式で`$GITHUB_OUTPUT`に書き出すと、本ステップとしての出力を`steps.ステップ名.outputs.変数名`に格納できます。
スクリプトの結果を別のジョブから参照できるようにするために、`outputs.pgversion`を設定しジョブの出力としました。

## テスト実施
テストを実行するためのジョブ`test`を設定しました。

#### 前半
```yml
jobs:
  test:
    needs: detect-pgversion
    env:
      SQLITE_VERSION : "3420000"
      SQLITE_YEAR: "2023"
    strategy:
      fail-fast: false
      matrix:
        pg: ${{ fromJSON(needs.detect-pgversion.outputs.pgversion) }}
        config: ["default", "postgis"]
        
    name: Test on PostgreSQL ${{ matrix.pg }}, ${{ matrix.config }} mode
    runs-on: ubuntu-22.04
```
本ジョブを実行する前にPostgreSQLのバージョンを知りたいので、`needs`で`detect-pgversion`ジョブを設定しました。  

複数のパラメータを使ってジョブを動かすにはmatrix機能を使います。
この機能は、配列で複数のパラメータを指定すると、その指定値分だけジョブを繰り返し実行できます。配列が複数ある場合、その組み合わせの数だけジョブが実行されます。
今回の場合、PostgreSQLのバージョンが5種類あり、テストパターンが2種類あります。
その情報を上記コードのように`strategy.matrix`に設定すると、`${{ matrix.pg }}`および`${{ matrix.config }}`という変数を使って参照できるようになり、5✕2の10パターンのジョブを動かせます（要件3）。

いずれかのテストで失敗しても中断せずに、10パターン全部を動かすために、`strategy:fail-fast: false`を設定しました。

#### 後半

```yml
jobs:
  # （ここに前半部分がある）
  test:
    steps:
      - uses: actions/checkout@v4
      
      ### 中略 ###

      - name: build PostgreSQL ${{ matrix.pg }} with ${{ matrix.config }}
        run: bash GitHubActions/build_postgres.sh ${{ matrix.pg }}

      ### 中略 ###

      - name: build sqlite_fdw, ${{ matrix.config }} mode
        run: |
          bash GitHubActions/build_sqlite_fdw.sh ${{ matrix.pg }} ${{ matrix.config }}

      - name: execute sqlite_fdw test
        run: bash GitHubActions/execute_test.sh ${{ matrix.pg }} ${{ matrix.config }}

      - name: download output files (regression.diffs)
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.pg }}-${{ matrix.config }}-test-results
          path: |
            workdir/postgresql-${{ matrix.pg }}/contrib/sqlite_fdw/regression.diffs
            workdir/postgresql-${{ matrix.pg }}/contrib/sqlite_fdw/regression.out
            workdir/postgresql-${{ matrix.pg }}/contrib/sqlite_fdw/results
          retention-days: 7
```

環境構築とテスト実行のためのスクリプト（`build_postgres.sh`, `build_sqlite_fdw.sh`, `execute_test.sh `）を用意しました。
これらはバージョン情報を引数から受け取れる仕様となっていて、本ジョブの前半に設定した変数（`${{ matrix.pg }}`および`${{ matrix.config }}`）を使って、バージョン情報を与えます。

最後に、実行した結果を`artifact`機能を使って保存します（要件4）。
この機能を使うと、ジョブ実行環境上のファイルをあとでGitHubからダウンロードできるようになります。
今回は、テストの実行ログである3つ（regression.diffsファイル, regression.outファイル, resultsディレクトリ）を保存するように設定しました。

なお、エラー発生時のみファイルを保存するように`if: failure()`を設定しました。
（`execute_test.sh`は、テストでエラーが発生すると1が返るようになっています）

![artifact.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581362/65acdee6-37f8-6bcb-038e-f13b8e06b195.png)

## おわりに

プルリクエストのコードに対して自動でリグレッションテストを実行できるようになりました。
コントリビューターの方も問題を迅速に把握できるとともに、我々メンテナの負担を少し軽減できました。

なお、GitHub actionsは、プライベートリポジトリで使う場合は有料なのでご注意ください（[詳細はこちら](https://docs.github.com/ja/billing/managing-billing-for-your-products/managing-billing-for-github-actions/about-billing-for-github-actions)）。

SQLite FDW：https://github.com/pgspider/sqlite_fdw/  
本記事で紹介した設定ファイル：https://github.com/pgspider/sqlite_fdw/blob/master/.github/workflows/CI.yml