extra-test-suite · master
Docs · CI integration

Docker Compose and other CI

Running the suite outside GitHub Actions — GitLab CI, Jenkins, CircleCI, local dev. The plain Docker Compose pattern.

Outside GitHub Actions, the canonical pattern is clone, pull, compose up.

The two compose files

File Purpose
docker-compose.yml Base — uses image: + build: dual mode
docker-compose.ci.yml CI override — disables restart

For CI: use both, in that order:

bash terminal — CI start
git clone https://github.com/php-opcua/extra-test-suite.git
cd extra-test-suite

TAG=v1.1.0 docker compose -f docker-compose.yml -f docker-compose.ci.yml pull
TAG=v1.1.0 docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d

Wait for both ports

The action handles this internally. Outside the action, your CI job needs to wait:

bash wait loop
for port in 24840 24841 24842; do
  for i in $(seq 1 30); do
    nc -z localhost "$port" 2>/dev/null && break
    sleep 1
  done
done

GitLab CI

text .gitlab-ci.yml
integration-tests:
  image: docker:24
  services:
    - docker:24-dind
  variables:
    TAG: v1.1.0
  before_script:
    - apk add --no-cache git docker-cli-compose netcat-openbsd
    - git clone https://github.com/php-opcua/extra-test-suite.git /tmp/extras
    - cd /tmp/extras
    - docker compose -f docker-compose.yml -f docker-compose.ci.yml pull
    - docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d
    - |
      for port in 24840 24841 24842; do
        for i in $(seq 1 30); do
          nc -z localhost "$port" 2>/dev/null && break
          sleep 1
        done
      done
  script:
    - cd "$CI_PROJECT_DIR"
    - vendor/bin/pest --group=integration
  after_script:
    - cd /tmp/extras
    - docker compose -f docker-compose.yml -f docker-compose.ci.yml down

docker:dind provides the Docker daemon the runner needs.

Jenkins (declarative pipeline)

text Jenkinsfile (excerpt)
pipeline {
  agent any
  environment { TAG = 'v1.1.0' }
  stages {
    stage('Start extras') {
      steps {
        sh '''
          git clone https://github.com/php-opcua/extra-test-suite.git extras
          cd extras
          docker compose -f docker-compose.yml -f docker-compose.ci.yml pull
          docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d
        '''
        sh '''
          for port in 24840 24841 24842; do
            for i in $(seq 1 30); do
              nc -z localhost "$port" 2>/dev/null && break
              sleep 1
            done
          done
        '''
      }
    }
    stage('Test') {
      steps {
        sh 'vendor/bin/pest --group=integration'
      }
    }
  }
  post {
    always {
      sh '''
        cd extras
        docker compose -f docker-compose.yml -f docker-compose.ci.yml down
      '''
    }
  }
}

CircleCI

text .circleci/config.yml
jobs:
  test:
    machine:
      image: ubuntu-2204:current
    environment:
      TAG: v1.1.0
    steps:
      - checkout
      - run:
          name: Start extras
          command: |
            git clone https://github.com/php-opcua/extra-test-suite.git /tmp/extras
            cd /tmp/extras
            docker compose -f docker-compose.yml -f docker-compose.ci.yml pull
            docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d
      - run:
          name: Wait for servers
          command: |
            for port in 24840 24841 24842; do
              for i in $(seq 1 30); do
                nc -z localhost "$port" 2>/dev/null && break
                sleep 1
              done
            done
      - run:
          name: Tests
          command: vendor/bin/pest --group=integration
      - run:
          name: Teardown
          command: |
            cd /tmp/extras
            docker compose -f docker-compose.yml -f docker-compose.ci.yml down

Local development

For working on a client library that targets the suite:

bash local
git clone https://github.com/php-opcua/extra-test-suite.git
cd extra-test-suite
docker compose up -d

The base compose file uses restart: unless-stopped, so the containers survive host reboots. Start once, leave running.

Or pull pre-built:

bash local pull
TAG=v1.1.0 docker compose pull
TAG=v1.1.0 docker compose up -d

Cleanup

docker compose down -v

-v removes anonymous volumes — important because open62541-all-security's Dockerfile declares VOLUME ["/certs"], which Docker backs with an anonymous volume by default. Without -v that volume (and the server cert in it) survives down, and stale fingerprints across runs can cause cache mismatches in your tests.

Image freshness

The publish workflow tags images with both :vX.Y.Z (immutable) and :latest (the last vX.Y.Z published). CI should pin :vX.Y.Z for reproducibility.

Combining with the main suite

For Jenkins / GitLab / etc., run both suites in parallel:

bash both suites
# Pull and start main suite
git clone https://github.com/php-opcua/uanetstandard-test-suite.git /tmp/main
cd /tmp/main
docker compose -f docker-compose.yml -f docker-compose.ci.yml pull
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d

# Pull and start extras
git clone https://github.com/php-opcua/extra-test-suite.git /tmp/extras
cd /tmp/extras
docker compose -f docker-compose.yml -f docker-compose.ci.yml pull
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d

# Wait for all ports
for port in 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4851 24840 24841 24842; do
  for i in $(seq 1 60); do
    nc -z localhost "$port" 2>/dev/null && break
    sleep 1
  done
done

# Run tests
vendor/bin/pest --group=integration

# Cleanup both
cd /tmp/main && docker compose -f docker-compose.yml -f docker-compose.ci.yml down
cd /tmp/extras && docker compose -f docker-compose.yml -f docker-compose.ci.yml down

The ports don't overlap (4840-4851 vs 24840-24842), so this just works.