その他の CI システム

TestDetta は、.NET と git を実行できるあらゆる CI で動作します。GitHub Actions 以外では、2 つのコマンドと 1 つのフォルダーで対応できます。

<maps> は通常のフォルダーです。CI がパイプライン間でファイルを保持できる場所であれば、どこに置いてもかまいません。

TestDetta 自体がマップのためにネットワークを使用することはありません。

すべての環境で必要なもの

インストール

dotnet tool install --global <package>

パッケージ名はリリース時に決定します。それまでは dotnet build src/TestDetta.Cli -c Release -o testdetta-bin でソースから TestDetta をビルドし、以下で td と書かれている箇所では代わりに dotnet testdetta-bin/TestDetta.Cli.dll を呼び出してください。

GitLab CI

variables:
  GIT_DEPTH: "0"                    # full history
  TESTDETTA_MAPS: /srv/testdetta-maps/$CI_PROJECT_PATH   # a disk the runners share; or sync with object storage

record-coverage:
  stage: test
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - td record --maps-dir "$TESTDETTA_MAPS"

affected-tests:
  stage: test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - git fetch --no-tags origin "+refs/heads/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:refs/remotes/origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
    - td test --base "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --maps-dir "$TESTDETTA_MAPS" --summary-file testdetta-summary.md
  artifacts:
    when: always
    paths: [testdetta-summary.md]

TESTDETTA_LICENSE は Settings → CI/CD → Variables で設定し、必要に応じてマスクと保護を有効にしてください。TestDetta は CI_PROJECT_VISIBILITY によってパブリックプロジェクトを判定します。

Jenkins

宣言型のマルチブランチパイプラインの例です。

pipeline {
  agent any
  environment {
    TESTDETTA_LICENSE = credentials('testdetta-license')          // a "secret text" credential
    TESTDETTA_MAPS = "/srv/testdetta-maps/${env.JOB_NAME.split('/')[0]}"
  }
  options { skipDefaultCheckout() }
  stages {
    stage('Checkout') {
      steps {
        checkout scmGit(branches: scm.branches, userRemoteConfigs: scm.userRemoteConfigs,
                        extensions: [cloneOption(shallow: false, depth: 0, noTags: true, reference: '')])
      }
    }
    stage('Record coverage') {
      when { branch 'main' }
      steps { sh 'td record --maps-dir "$TESTDETTA_MAPS"' }
    }
    stage('Affected tests') {
      when { changeRequest() }
      steps {
        sh 'git fetch --no-tags origin "+refs/heads/$CHANGE_TARGET:refs/remotes/origin/$CHANGE_TARGET"'
        sh 'td test --base "origin/$CHANGE_TARGET" --maps-dir "$TESTDETTA_MAPS"'
      }
    }
  }
}

Jenkins は、リモート URL に資格情報を含めてクローンすることがよくあります。TestDetta はライセンスのスコープと照合する前にそれを取り除き、ログに出力することはありません。リモートがライセンスの対象リポジトリを示していない場合(ローカルのミラーなど)は、git.acme.corp/scm/pay/app のように TESTDETTA_LICENSE_SCOPE を設定してください。

Azure DevOps Pipelines

trigger: [main]
pr: [main]

steps:
  - checkout: self
    fetchDepth: 0
  - script: td record --maps-dir "$(TESTDETTA_MAPS)"
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    env:
      TESTDETTA_LICENSE: $(TestDettaLicense)          # a secret pipeline variable
  - script: |
      target="${SYSTEM_PULLREQUEST_TARGETBRANCH#refs/heads/}"
      git fetch --no-tags origin "+refs/heads/$target:refs/remotes/origin/$target"
      td test --base "origin/$target" --maps-dir "$(TESTDETTA_MAPS)" --summary-file "$(Agent.TempDirectory)/testdetta.md"
      echo "##vso[task.uploadsummary]$(Agent.TempDirectory)/testdetta.md"
    condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
    env:
      TESTDETTA_LICENSE: $(TestDettaLicense)

セルフホステッドエージェントでは、TESTDETTA_MAPS は共有フォルダーを指します。Microsoft ホステッドエージェントでは、Azure Blob Storage と同期してください(オブジェクトストレージを参照)。

Bitbucket Pipelines

clone:
  depth: full

pipelines:
  branches:
    main:
      - step:
          script:
            - td record --maps-dir maps
            - ./sync-maps.sh upload maps        # see "Object storage"
  pull-requests:
    '**':
      - step:
          script:
            - git fetch --no-tags origin "+refs/heads/$BITBUCKET_PR_DESTINATION_BRANCH:refs/remotes/origin/$BITBUCKET_PR_DESTINATION_BRANCH"
            - ./sync-maps.sh download maps "origin/$BITBUCKET_PR_DESTINATION_BRANCH"
            - td test --base "origin/$BITBUCKET_PR_DESTINATION_BRANCH" --maps-dir maps

TESTDETTA_LICENSE はセキュリティで保護されたリポジトリ変数として設定してください。

共有ディスクの代わりにオブジェクトストレージを使う

ホステッドランナーには共有ディスクがありません。記録の後に、新しいフォルダーをアップロードします。テストの前には、使用できるフォルダー、つまりマージベースとその前の最大 50 個の first-parent コミットのフォルダーだけをダウンロードします。新しいものから順にダウンロードを試み、最初に存在したものの時点で止めます。以下は S3 の例です。cp と ls を備えたツールであれば、同じ方法で使えます。

#!/usr/bin/env bash
# sync-maps.sh upload <maps>          | after td record on the default branch
# sync-maps.sh download <maps> <base> | before td test on a pull request
set -euo pipefail
remote="s3://my-bucket/testdetta-maps/my-repository"
case "$1" in
  upload)
    aws s3 cp --recursive "$2/$(git rev-parse HEAD)" "$remote/$(git rev-parse HEAD)" ;;
  download)
    for sha in $(git rev-list --first-parent -n 51 "$(git merge-base "$3" HEAD)"); do
      if aws s3 ls "$remote/$sha/" > /dev/null 2>&1; then
        aws s3 cp --recursive "$remote/$sha" "$2/$sha"
        break
      fi
    done ;;
esac

マップがない場合

セットアップ直後の最初のプルリクエストや、大きく遅れているプルリクエストでは、到達できる範囲にマップが見つかりません。その場合 TestDetta は名前だけで選択します。安全ですが選択されるテストは多くなり、その旨をログに出力します。既定のブランチで td record が実行されると、選択は精密になります。