Otros sistemas de CI

TestDetta funciona en cualquier CI que ejecute .NET y git. Fuera de GitHub Actions, bastan dos comandos y una carpeta.

<maps> es una carpeta normal. Guárdala donde tu CI pueda conservar archivos entre pipelines:

TestDetta en sí nunca usa la red para los mapas.

Qué necesita cualquier configuración

Instalación

dotnet tool install --global <package>

El nombre del paquete se fijará en el lanzamiento. Hasta entonces, compila TestDetta desde el código fuente con dotnet build src/TestDetta.Cli -c Release -o testdetta-bin y llama a dotnet testdetta-bin/TestDetta.Cli.dll allí donde aparezca td más abajo.

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]

Define TESTDETTA_LICENSE en Settings → CI/CD → Variables, enmascarada y protegida según sea necesario. TestDetta detecta los proyectos públicos mediante CI_PROJECT_VISIBILITY.

Jenkins

Un pipeline multibranch declarativo:

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 suele clonar con credenciales en la URL del remoto. TestDetta las elimina antes de comparar con el ámbito de la licencia y nunca las registra. Si el remoto no nombra el repositorio que cubre la licencia (por ejemplo, un mirror local), define TESTDETTA_LICENSE_SCOPE, como git.acme.corp/scm/pay/app.

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 apunta a un recurso compartido en los agentes autohospedados. En los agentes hospedados por Microsoft, sincronízala con Azure Blob Storage (consulta Almacenamiento de objetos).

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

Define TESTDETTA_LICENSE como variable de repositorio protegida (secured).

Almacenamiento de objetos en lugar de un disco compartido

En los runners hospedados no hay disco compartido. Sube la carpeta nueva después del registro. Antes de las pruebas, descarga solo las carpetas que se pueden usar: la merge base y hasta 50 commits de primer padre anteriores a ella. Descárgalas de la más reciente a la más antigua y detente en la primera que exista. Un ejemplo para S3; cualquier herramienta con cp y ls funciona igual:

#!/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

Cuando no hay mapa

Las primeras pull requests tras la configuración, o las que están muy atrasadas, no encuentran ningún mapa a su alcance. TestDetta selecciona entonces solo por nombre, lo cual es seguro pero selecciona más, y lo indica en su registro. La selección se vuelve precisa en cuanto td record se ha ejecutado en la rama predeterminada.