diff --git a/.github/CI_VERSIONS.md b/.github/CI_VERSIONS.md new file mode 100644 index 0000000000..5a03756022 --- /dev/null +++ b/.github/CI_VERSIONS.md @@ -0,0 +1,15 @@ +# CI version pins + +Some versions used by CI are pinned manually and must be kept current. This +file is the checklist of what needs periodic review and where each pin lives. + +| What | Version | Defined in | Notes | +|------|---------|------------|-------| +| Java (JDK) | `21` | `.github/actions/setup-java/action.yml` | Single source of truth for the CI JDK. Must match `maven.compiler.source`/`maven.compiler.target` in `pom.xml`. Review when the project adopts a new LTS. | +| Elasticsearch | `8.11.2` | `.github/workflows/_integration-test.yml` and `services/save-and-restore/docker-compose.yml` | Service container for the save-and-restore integration tests. Update both files together. | + +## Automatically maintained + +GitHub Action versions (the `uses:` refs in `.github/workflows/*` and +`.github/actions/**`) are bumped by Dependabot — see `.github/dependabot.yml`. +These do **not** need manual tracking here. diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml new file mode 100644 index 0000000000..fa997d077e --- /dev/null +++ b/.github/actions/setup-java/action.yml @@ -0,0 +1,11 @@ +name: Set up Java +description: Set up the project's pinned JDK with Maven dependency caching. + +runs: + using: composite + steps: + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 + with: + distribution: temurin + java-version: '21' + cache: maven diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..93bd2548f5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + # Auto-PR bumps for GitHub Action versions referenced in .github/workflows/* + # and in the local composite action (.github/actions/**). The JDK and + # Elasticsearch pins are not visible to Dependabot; see .github/CI_VERSIONS.md. + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml new file mode 100644 index 0000000000..c8eb18b17e --- /dev/null +++ b/.github/workflows/_build.yml @@ -0,0 +1,36 @@ +name: Build + +on: + workflow_call: + inputs: + os: + description: Runner to build on. + type: string + default: ubuntu-latest + upload-artifacts: + description: Upload the product tarball/zip (used by the master build). + type: boolean + default: false + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ inputs.os }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + persist-credentials: false + - name: Set up Java + uses: ./.github/actions/setup-java + - name: Build + run: mvn --batch-mode install -DskipTests + - name: Archive build artifacts + if: ${{ inputs.upload-artifacts }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: Phoebus product ${{ inputs.os }} + path: | + ${{ github.workspace }}/phoebus-product/target/*.tar.gz + ${{ github.workspace }}/phoebus-product/target/*.zip diff --git a/.github/workflows/_docker-image.yml b/.github/workflows/_docker-image.yml new file mode 100644 index 0000000000..0289e29ec5 --- /dev/null +++ b/.github/workflows/_docker-image.yml @@ -0,0 +1,58 @@ +name: Docker image + +on: + workflow_call: + inputs: + image-suffix: + description: Image name under ghcr.io/// (e.g. service-alarm-server). + required: true + type: string + context: + description: Docker build context directory. + required: true + type: string + maven-args: + description: Maven goals/flags for the package step. + type: string + default: --update-snapshots package + +permissions: + contents: read + packages: write + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + persist-credentials: false + - name: Set up Java + uses: ./.github/actions/setup-java + - name: Build with Maven + run: mvn --batch-mode $MAVEN_ARGS + env: + MAVEN_ARGS: ${{ inputs.maven-args }} + - name: Login to the registry + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract meta-data for Docker + id: meta + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 + with: + images: ghcr.io/${{ github.repository }}/${{ inputs.image-suffix }} + - name: Set up Docker Build + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 + - name: Build and publish the Docker image + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf + with: + context: ${{ inputs.context }} + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/_integration-test.yml b/.github/workflows/_integration-test.yml new file mode 100644 index 0000000000..966c8f6505 --- /dev/null +++ b/.github/workflows/_integration-test.yml @@ -0,0 +1,43 @@ +name: Integration test + +on: + workflow_call: + +permissions: + contents: read + +jobs: + integration-test: + runs-on: ubuntu-latest + services: + # TODO swap to testcontainers for integration tests + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.2 + env: + discovery.type: single-node + xpack.security.enabled: "false" + ports: + - 9200:9200 + options: >- + --health-cmd "curl -s http://localhost:9200/_cluster/health || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 30 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + persist-credentials: false + - name: Set up Java + uses: ./.github/actions/setup-java + - name: Integration tests (Elasticsearch-backed) + run: > + mvn --batch-mode install -P it-tests,docker-tests + -Dskip-executable-jar + -pl services/save-and-restore -am + - name: Upload integration test reports + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: integration-test-reports + path: services/save-and-restore/target/failsafe-reports/** + if-no-files-found: ignore diff --git a/.github/workflows/_lint.yml b/.github/workflows/_lint.yml new file mode 100644 index 0000000000..5a18d8fc6f --- /dev/null +++ b/.github/workflows/_lint.yml @@ -0,0 +1,17 @@ +name: pre-commit + +on: + workflow_call: + +permissions: + contents: read + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd \ No newline at end of file diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml new file mode 100644 index 0000000000..80fdc60fa3 --- /dev/null +++ b/.github/workflows/_test.yml @@ -0,0 +1,41 @@ +name: Test + +on: + workflow_call: + inputs: + name: + description: Name used for the uploaded report artifact. + type: string + required: true + run: + description: Command that runs the tests. + type: string + required: true + report-path: + description: Glob for the test reports to upload. + type: string + required: true + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + persist-credentials: false + - name: Set up Java + uses: ./.github/actions/setup-java + - name: Test + run: $TEST_COMMAND + env: + TEST_COMMAND: ${{ inputs.run }} + - name: Upload test reports + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + if: always() + with: + name: ${{ inputs.name }}-reports + path: ${{ inputs.report-path }} + if-no-files-found: ignore diff --git a/.github/workflows/_ui-test.yml b/.github/workflows/_ui-test.yml new file mode 100644 index 0000000000..249c1e20fb --- /dev/null +++ b/.github/workflows/_ui-test.yml @@ -0,0 +1,15 @@ +name: UI test + +on: + workflow_call: + +permissions: + contents: read + +jobs: + ui-test: + uses: ./.github/workflows/_test.yml + with: + name: ui-test + run: xvfb-run -a mvn --batch-mode --fail-at-end verify -P ui-tests + report-path: '**/target/failsafe-reports/**' diff --git a/.github/workflows/_unit-test.yml b/.github/workflows/_unit-test.yml new file mode 100644 index 0000000000..cde74577d6 --- /dev/null +++ b/.github/workflows/_unit-test.yml @@ -0,0 +1,15 @@ +name: Unit test + +on: + workflow_call: + +permissions: + contents: read + +jobs: + unit-test: + uses: ./.github/workflows/_test.yml + with: + name: unit-test + run: mvn --batch-mode --fail-at-end verify + report-path: '**/target/surefire-reports/**' diff --git a/.github/workflows/alarm-logger-docker-image.yml b/.github/workflows/alarm-logger-docker-image.yml deleted file mode 100644 index 94071123e6..0000000000 --- a/.github/workflows/alarm-logger-docker-image.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Alarm Logger Docker Image CI - -on: - push: - branches: [ "master" ] - paths: services/alarm-logger/** - tags: - - '**' - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}/service-alarm-logger - -jobs: - build-server: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Maven and Java Action - uses: s4u/setup-maven-action@v1.18.0 - with: - java-version: '21' - maven-version: '3.9.6' - - name: Build - run: mvn --batch-mode install -DskipTests - build-and-push-image: - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn --batch-mode --update-snapshots package - - name: Login to the registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract meta-data for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Set up Docker Build - uses: docker/setup-buildx-action@v3 - - name: Build and publish the Docker image - uses: docker/build-push-action@v5 - with: - context: services/alarm-logger - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/alarm-server-docker-image.yml b/.github/workflows/alarm-server-docker-image.yml deleted file mode 100644 index 4fe2f28281..0000000000 --- a/.github/workflows/alarm-server-docker-image.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Alarm Server Docker Image CI - -on: - push: - branches: [ "master" ] - paths: services/alarm-server/** - tags: - - '**' - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}/service-alarm-server - -jobs: - build-server: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Maven and Java Action - uses: s4u/setup-maven-action@v1.18.0 - with: - java-version: '21' - maven-version: '3.9.6' - - name: Build - run: mvn --batch-mode install -DskipTests - build-and-push-image: - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn --batch-mode --update-snapshots package - - name: Login to the registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract meta-data for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Set up Docker Build - uses: docker/setup-buildx-action@v3 - - name: Build and publish the Docker image - uses: docker/build-push-action@v5 - with: - context: services/alarm-server - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml new file mode 100644 index 0000000000..66b78cc25b --- /dev/null +++ b/.github/workflows/branch.yml @@ -0,0 +1,19 @@ +name: Branch + +on: + push: + branches-ignore: + - master + +permissions: + contents: read + +concurrency: + group: branch-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + uses: ./.github/workflows/_lint.yml + build: + uses: ./.github/workflows/_build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 00ee599c99..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Phoebus build - -on: - push: - branches-ignore: - - 'master' - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Maven and Java Action - uses: s4u/setup-maven-action@v1.18.0 - with: - java-version: '21' - maven-version: '3.9.6' - - name: Build - run: mvn --batch-mode install -DskipTests \ No newline at end of file diff --git a/.github/workflows/build_latest.yml b/.github/workflows/build_latest.yml deleted file mode 100644 index 72bc62f3be..0000000000 --- a/.github/workflows/build_latest.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Phoebus build - -on: - push: - branches: - - master - -jobs: - build: - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] - - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Setup Maven and Java Action - uses: s4u/setup-maven-action@v1.18.0 - with: - java-version: '21' - maven-version: '3.9.6' - - name: Build - run: mvn --batch-mode install -DskipTests - - - name: Archive build artifacts - uses: actions/upload-artifact@v4 - with: - name: Phoebus product ${{ matrix.os }} - path: | - ${{ github.workspace }}/phoebus-product/target/*.tar.gz - ${{ github.workspace }}/phoebus-product/target/*.zip diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml new file mode 100644 index 0000000000..a112474887 --- /dev/null +++ b/.github/workflows/merge.yml @@ -0,0 +1,42 @@ +name: Merge to master + +on: + push: + branches: + - master + +permissions: + contents: read + +jobs: + lint: + uses: ./.github/workflows/_lint.yml + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + uses: ./.github/workflows/_build.yml + with: + os: ${{ matrix.os }} + upload-artifacts: true + unit-test: + uses: ./.github/workflows/_unit-test.yml + ui-test: + uses: ./.github/workflows/_ui-test.yml + integration-test: + uses: ./.github/workflows/_integration-test.yml + + docker-image: + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + service: [alarm-server, alarm-logger, save-and-restore, scan-server] + uses: ./.github/workflows/_docker-image.yml + with: + image-suffix: service-${{ matrix.service }} + context: services/${{ matrix.service }} + maven-args: ${{ matrix.service == 'scan-server' && '-Pexecutable-jar --update-snapshots package' || '--update-snapshots package' }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..1ce439e35e --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,27 @@ +name: PR + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pr-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + lint: + uses: ./.github/workflows/_lint.yml + build: + uses: ./.github/workflows/_build.yml + unit-test: + needs: build + uses: ./.github/workflows/_unit-test.yml + ui-test: + needs: build + uses: ./.github/workflows/_ui-test.yml + integration-test: + needs: build + uses: ./.github/workflows/_integration-test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..3ecf7a9b10 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +name: Release + +on: + push: + tags: + - '**' + +permissions: + contents: read + +jobs: + docker-image: + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + service: [alarm-server, alarm-logger, save-and-restore, scan-server] + uses: ./.github/workflows/_docker-image.yml + with: + image-suffix: service-${{ matrix.service }} + context: services/${{ matrix.service }} + maven-args: ${{ matrix.service == 'scan-server' && '-Pexecutable-jar --update-snapshots package' || '--update-snapshots package' }} diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml new file mode 100644 index 0000000000..2f961945c6 --- /dev/null +++ b/.github/workflows/report.yml @@ -0,0 +1,35 @@ +name: Test report + +on: + # Reports must run with write permissions that PRs from forks cannot grant, + # so they are published from a separate workflow_run-triggered workflow. The + # job only downloads artifacts and posts reports; it never runs PR code. + workflow_run: # zizmor: ignore[dangerous-triggers] + workflows: + - PR + - Merge to master + types: + - completed + +permissions: + contents: read + actions: read + checks: write + pull-requests: write + +jobs: + report: + if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} + runs-on: ubuntu-latest + steps: + - name: Download test report artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + pattern: '*test-reports' + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + - name: Publish test report + uses: mikepenz/action-junit-report@d9f48fc87bc235f7e214acf696ca5abc0a986f16 + with: + report_paths: '**/TEST-*.xml' + commit: ${{ github.event.workflow_run.head_sha }} diff --git a/.github/workflows/save-and-restore-docker-image.yml b/.github/workflows/save-and-restore-docker-image.yml deleted file mode 100644 index 56d2a3734e..0000000000 --- a/.github/workflows/save-and-restore-docker-image.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Save-And-Restore Docker Image CI - -on: - push: - branches: [ "master" ] - paths: services/save-and-restore/** - tags: - - '**' - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}/service-save-and-restore - -jobs: - build-and-push-image: - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn --batch-mode --update-snapshots package - - name: Login to the registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract meta-data for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Set up Docker Build - uses: docker/setup-buildx-action@v3 - - name: Build and publish the Docker image - uses: docker/build-push-action@v5 - with: - context: services/save-and-restore - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/scan-server-docker-image.yml b/.github/workflows/scan-server-docker-image.yml deleted file mode 100644 index f11f8a065f..0000000000 --- a/.github/workflows/scan-server-docker-image.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Scan Server Docker Image CI - -on: - workflow_dispatch: - push: - branches: [ "master" ] - paths: services/scan-server/** - tags: - - '**' - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}/service-scan-server - -jobs: - build-server: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Maven and Java Action - uses: s4u/setup-maven-action@v1.18.0 - with: - java-version: '17' - maven-version: '3.9.6' - - name: Build - run: mvn -Pexecutable-jar --batch-mode install -DskipTests - build-and-push-image: - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -Pexecutable-jar --batch-mode --update-snapshots package - - name: Login to the registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract meta-data for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Set up Docker Build - uses: docker/setup-buildx-action@v3 - - name: Build and publish the Docker image - uses: docker/build-push-action@v5 - with: - context: services/scan-server - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..3e189a6d82 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-yaml + # TODO add end of file fixer and whitespace fixer + - repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: v1.26.1 + hooks: + - id: zizmor \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1dc660ef2c..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -sudo: required -language: java -dist: xenial -jdk: - - openjdk21 - -services: - - xvfb - -before_script: - - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then export DISPLAY=:99.0 && /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16; fi - - MAVEN_OPTS="-Djava.awt.headless=true -Dtestfx.robot=glass -Dtestfx.headless=true -Dprism.order=sw -Dprism.text=t2k -Dtestfx.setup.timeout=2500" - -script: - - mvn clean install - -after_failure: - - find ./ -type d -name "surefire-reports" -print0 | xargs -0 -I {} find {} -iname "*.txt" -type f | xargs cat - - find . -type f -name "*.log" -print0 -exec cat {} \; diff --git a/README.md b/README.md index ebd686403c..34a4349184 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # phoebus -![GitHub Actions Status](https://github.com/ControlSystemStudio/phoebus/actions/workflows/build.yml/badge.svg) +![GitHub Actions Status](https://github.com/ControlSystemStudio/phoebus/actions/workflows/merge.yml/badge.svg) Phoebus is a framework and a collections of tools to monitor and operate large scale control systems, such as the ones in the accelerator community. Phoebus is an update of the Control System Studio toolset that removes dependencies on Eclipse RCP and SWT. diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmTableUpdatePerformanceTest.java b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmTableUpdatePerformanceFXTest.java similarity index 99% rename from app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmTableUpdatePerformanceTest.java rename to app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmTableUpdatePerformanceFXTest.java index 7728c5ca04..304ae561ec 100644 --- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmTableUpdatePerformanceTest.java +++ b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmTableUpdatePerformanceFXTest.java @@ -47,7 +47,7 @@ * @see Issue #3504 */ @SuppressWarnings("nls") -class AlarmTableUpdatePerformanceTest +class AlarmTableUpdatePerformanceFXTest { private static final int N = 150; // Large enough to expose O(N^2) but fast to run diff --git a/app/databrowser/src/test/java/org/csstudio/trends/databrowser3/persistence/XMLPersistenceTest.java b/app/databrowser/src/test/java/org/csstudio/trends/databrowser3/persistence/XMLPersistenceFXTest.java similarity index 99% rename from app/databrowser/src/test/java/org/csstudio/trends/databrowser3/persistence/XMLPersistenceTest.java rename to app/databrowser/src/test/java/org/csstudio/trends/databrowser3/persistence/XMLPersistenceFXTest.java index 429c19eb5a..10733c18ba 100644 --- a/app/databrowser/src/test/java/org/csstudio/trends/databrowser3/persistence/XMLPersistenceTest.java +++ b/app/databrowser/src/test/java/org/csstudio/trends/databrowser3/persistence/XMLPersistenceFXTest.java @@ -36,7 +36,7 @@ *

Tests are organized by feature area using nested test classes. */ @DisplayName("XMLPersistence") -class XMLPersistenceTest { +class XMLPersistenceFXTest { // --------------------------------------------------------------------------- // Constants diff --git a/app/diag/src/test/java/org/phoebus/app/diag/ui/FormulaTreeRootNodeTest.java b/app/diag/src/test/java/org/phoebus/app/diag/ui/FormulaTreeRootNodeFXTest.java similarity index 98% rename from app/diag/src/test/java/org/phoebus/app/diag/ui/FormulaTreeRootNodeTest.java rename to app/diag/src/test/java/org/phoebus/app/diag/ui/FormulaTreeRootNodeFXTest.java index 1612440d43..765c0eb057 100644 --- a/app/diag/src/test/java/org/phoebus/app/diag/ui/FormulaTreeRootNodeTest.java +++ b/app/diag/src/test/java/org/phoebus/app/diag/ui/FormulaTreeRootNodeFXTest.java @@ -13,7 +13,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; -public class FormulaTreeRootNodeTest { +public class FormulaTreeRootNodeFXTest { public FormulaFunction createFormula(String name, String description, String category) { return new FormulaFunction() { diff --git a/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/JFXUtilTest.java b/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/JFXUtilFXTest.java similarity index 99% rename from app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/JFXUtilTest.java rename to app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/JFXUtilFXTest.java index 2167b2d6d1..80df597697 100644 --- a/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/JFXUtilTest.java +++ b/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/JFXUtilFXTest.java @@ -21,7 +21,7 @@ * @author Kay Kasemir */ @SuppressWarnings("nls") -public class JFXUtilTest +public class JFXUtilFXTest { @Test diff --git a/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/SVGHelperTest.java b/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/SVGHelperFXTest.java similarity index 99% rename from app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/SVGHelperTest.java rename to app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/SVGHelperFXTest.java index 970a61b16c..fc63450bf1 100644 --- a/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/SVGHelperTest.java +++ b/app/display/representation-javafx/src/test/java/org/csstudio/display/builder/representation/javafx/SVGHelperFXTest.java @@ -29,7 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -public class SVGHelperTest { +public class SVGHelperFXTest { /** * Verifies that the SVG file can be loaded and that the created {@link Image} has diff --git a/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/util/GraphicsUtilsTest.java b/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/util/GraphicsUtilsFXTest.java similarity index 98% rename from app/rtplot/src/test/java/org/csstudio/javafx/rtplot/util/GraphicsUtilsTest.java rename to app/rtplot/src/test/java/org/csstudio/javafx/rtplot/util/GraphicsUtilsFXTest.java index e1954d6ed9..6f2702e6b2 100644 --- a/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/util/GraphicsUtilsTest.java +++ b/app/rtplot/src/test/java/org/csstudio/javafx/rtplot/util/GraphicsUtilsFXTest.java @@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class GraphicsUtilsTest { +public class GraphicsUtilsFXTest { @Test public void convertColor1(){ diff --git a/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/ui/DragNDropUtilTest.java b/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/ui/DragNDropUtilFXTest.java similarity index 99% rename from app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/ui/DragNDropUtilTest.java rename to app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/ui/DragNDropUtilFXTest.java index 458d0b19e3..0da08d5982 100644 --- a/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/ui/DragNDropUtilTest.java +++ b/app/save-and-restore/app/src/test/java/org/phoebus/applications/saveandrestore/ui/DragNDropUtilFXTest.java @@ -30,7 +30,7 @@ import static org.junit.jupiter.api.Assertions.*; -public class DragNDropUtilTest { +public class DragNDropUtilFXTest { private final Node folderTargetNode = Node.builder().uniqueId(UUID.randomUUID().toString()).nodeType(NodeType.FOLDER).build(); private final Node compositeSnapshotTargetNode = Node.builder().uniqueId(UUID.randomUUID().toString()).nodeType(NodeType.COMPOSITE_SNAPSHOT).build(); diff --git a/app/trends/rich-adapters/src/test/java/org/phoebus/apps/trends/rich/adapters/DataBrowserAdapterFactoryTest.java b/app/trends/rich-adapters/src/test/java/org/phoebus/apps/trends/rich/adapters/DataBrowserAdapterFactoryFXTest.java similarity index 98% rename from app/trends/rich-adapters/src/test/java/org/phoebus/apps/trends/rich/adapters/DataBrowserAdapterFactoryTest.java rename to app/trends/rich-adapters/src/test/java/org/phoebus/apps/trends/rich/adapters/DataBrowserAdapterFactoryFXTest.java index 5a205bad5c..2812e8f3ff 100644 --- a/app/trends/rich-adapters/src/test/java/org/phoebus/apps/trends/rich/adapters/DataBrowserAdapterFactoryTest.java +++ b/app/trends/rich-adapters/src/test/java/org/phoebus/apps/trends/rich/adapters/DataBrowserAdapterFactoryFXTest.java @@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; -public class DataBrowserAdapterFactoryTest { +public class DataBrowserAdapterFactoryFXTest { @Test public void testGetAdaptableObject(){ diff --git a/core/ui/pom.xml b/core/ui/pom.xml index 6a3b49b5cd..8945a6204b 100644 --- a/core/ui/pom.xml +++ b/core/ui/pom.xml @@ -23,13 +23,13 @@ org.testfx testfx-core - 4.0.13-alpha + 4.0.18 test org.testfx - testfx-junit - 4.0.13-alpha + testfx-junit5 + 4.0.18 test diff --git a/core/ui/src/test/java/org/phoebus/ui/dialog/ListSelectionUI.java b/core/ui/src/test/java/org/phoebus/ui/dialog/ListSelectionUI.java index 687096341c..527c8778a1 100644 --- a/core/ui/src/test/java/org/phoebus/ui/dialog/ListSelectionUI.java +++ b/core/ui/src/test/java/org/phoebus/ui/dialog/ListSelectionUI.java @@ -17,7 +17,7 @@ import javafx.stage.Stage; import org.junit.jupiter.api.Test; import org.phoebus.ui.javafx.ClearingTextField; -import org.testfx.framework.junit.ApplicationTest; +import org.testfx.framework.junit5.ApplicationTest; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; diff --git a/core/ui/src/test/java/org/phoebus/ui/docking/DockPaneTestUI.java b/core/ui/src/test/java/org/phoebus/ui/docking/DockPaneTestUI.java index b44602f5ef..f1e5aee0fb 100644 --- a/core/ui/src/test/java/org/phoebus/ui/docking/DockPaneTestUI.java +++ b/core/ui/src/test/java/org/phoebus/ui/docking/DockPaneTestUI.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.phoebus.security.authorization.AuthorizationService; import org.phoebus.ui.application.Messages; -import org.testfx.framework.junit.ApplicationTest; +import org.testfx.framework.junit5.ApplicationTest; import javafx.collections.ObservableList; import javafx.scene.Node; diff --git a/core/ui/src/test/java/org/phoebus/ui/docking/SplitDockTestUI.java b/core/ui/src/test/java/org/phoebus/ui/docking/SplitDockTestUI.java index de13d7a637..c9bd60fa03 100644 --- a/core/ui/src/test/java/org/phoebus/ui/docking/SplitDockTestUI.java +++ b/core/ui/src/test/java/org/phoebus/ui/docking/SplitDockTestUI.java @@ -4,7 +4,7 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.testfx.api.FxRobot; -import org.testfx.framework.junit.ApplicationTest; +import org.testfx.framework.junit5.ApplicationTest; import javafx.geometry.Bounds; import javafx.geometry.Point2D; diff --git a/core/ui/src/test/java/org/phoebus/ui/javafx/ImageCacheTest.java b/core/ui/src/test/java/org/phoebus/ui/javafx/ImageCacheFXTest.java similarity index 98% rename from core/ui/src/test/java/org/phoebus/ui/javafx/ImageCacheTest.java rename to core/ui/src/test/java/org/phoebus/ui/javafx/ImageCacheFXTest.java index 09b96a873a..a6d52a32f0 100644 --- a/core/ui/src/test/java/org/phoebus/ui/javafx/ImageCacheTest.java +++ b/core/ui/src/test/java/org/phoebus/ui/javafx/ImageCacheFXTest.java @@ -25,7 +25,7 @@ * @author Kay Kasemir */ @SuppressWarnings("nls") -public class ImageCacheTest extends Application +public class ImageCacheFXTest extends Application { @Override public void start(final Stage stage) throws Exception diff --git a/core/ui/src/test/java/org/phoebus/ui/javafx/JFXUtilTest.java b/core/ui/src/test/java/org/phoebus/ui/javafx/JFXUtilFXTest.java similarity index 97% rename from core/ui/src/test/java/org/phoebus/ui/javafx/JFXUtilTest.java rename to core/ui/src/test/java/org/phoebus/ui/javafx/JFXUtilFXTest.java index 9a2aa03187..51e8829281 100644 --- a/core/ui/src/test/java/org/phoebus/ui/javafx/JFXUtilTest.java +++ b/core/ui/src/test/java/org/phoebus/ui/javafx/JFXUtilFXTest.java @@ -23,7 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class JFXUtilTest { +public class JFXUtilFXTest { @Test public void testWebRGB(){ diff --git a/core/ui/src/test/java/org/phoebus/ui/javafx/svg/SVGTranscoderTest.java b/core/ui/src/test/java/org/phoebus/ui/javafx/svg/SVGTranscoderFXTest.java similarity index 98% rename from core/ui/src/test/java/org/phoebus/ui/javafx/svg/SVGTranscoderTest.java rename to core/ui/src/test/java/org/phoebus/ui/javafx/svg/SVGTranscoderFXTest.java index 18648101a3..2910b320ec 100644 --- a/core/ui/src/test/java/org/phoebus/ui/javafx/svg/SVGTranscoderTest.java +++ b/core/ui/src/test/java/org/phoebus/ui/javafx/svg/SVGTranscoderFXTest.java @@ -27,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -public class SVGTranscoderTest { +public class SVGTranscoderFXTest { /** * Verifies that the SVG file can be loaded and that the created {@link Image} has diff --git a/pom.xml b/pom.xml index 556891b64c..36ff21f56e 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,11 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M7 + + + **/*FXTest.java + + org.apache.maven.plugins @@ -206,8 +211,10 @@ false - + ui-tests @@ -215,12 +222,36 @@ + + + maven-surefire-plugin + + true + + maven-failsafe-plugin + false **/*UI.java + **/*FXTest.java + + + true + glass + true + sw + t2k + 2500 + @@ -241,6 +272,7 @@ **/*UI.java **/*IT.java + **/*FXTest.java diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index e7a59d62d0..61f8a0aca1 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -196,6 +196,21 @@ true + + + + maven-failsafe-plugin + + + IT + + + + diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java index 452c472318..2dd4d54e4e 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java @@ -38,7 +38,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Date; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -96,7 +95,6 @@ public class ElasticConfig { private ElasticsearchClient client; - private static final AtomicBoolean esInitialized = new AtomicBoolean(); private static final Node ROOT_NODE; @@ -143,11 +141,12 @@ public ElasticsearchClient getClient() { jacksonJsonpMapper ); client = new ElasticsearchClient(transport); - //esInitialized.set(!Boolean.parseBoolean(createIndices)); - if (esInitialized.compareAndSet(false, true)) { - elasticIndexValidation(client); - elasticIndexInitialization(client); - } + // Each ElasticConfig bean (i.e. each Spring context) ensures its own indices and + // root node exist. Both helpers are idempotent (they check existence before + // creating), so this is safe to run whenever a new client is built — in particular + // it lets integration tests with isolated, per-class indices each get their own root. + elasticIndexValidation(client); + elasticIndexInitialization(client); } return client; } diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java new file mode 100644 index 0000000000..1a9bd0f46f --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/AbstractElasticsearchIT.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2026 European Spallation Source ERIC. + */ + +package org.phoebus.service.saveandrestore; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.phoebus.service.saveandrestore.persistence.config.ElasticConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.TestPropertySource; + +import java.util.List; +import java.util.UUID; + +/** + * Base class for save-and-restore integration tests run against a live Elasticsearch 8.x instance + * (Spring profile {@code IT}). + * + *

Every concrete IT class gets its own randomly named set of Elasticsearch indices and its own + * freshly created root folder node, so classes are fully isolated and cannot pollute one another + * (e.g. a bulk {@code deleteAll()} in one class can no longer wipe the root that another class + * depends on). {@link DirtiesContext} evicts the Spring context after each class: the context + * cache key is derived from the {@link DynamicPropertySource} method (not the random values it + * produces), so without eviction all subclasses would share a single context and a single index + * set. Evicting forces the next class to rebuild the context, re-run {@code randomIndices} for a + * fresh suffix, and re-create its root. The throwaway indices are dropped once the class finishes. + */ +@TestInstance(Lifecycle.PER_CLASS) +@SpringBootTest +@ContextConfiguration(classes = ElasticConfig.class) +@TestPropertySource(locations = "classpath:test_application.properties") +@Profile("IT") +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public abstract class AbstractElasticsearchIT { + + private static final List INDEX_PROPERTY_KEYS = List.of( + "elasticsearch.tree_node.index", + "elasticsearch.configuration_node.index", + "elasticsearch.snapshot_node.index", + "elasticsearch.composite_snapshot_node.index", + "elasticsearch.filter.index"); + + /** + * Invoked once per subclass context build, producing a fresh suffix each time so each IT class + * resolves to a distinct set of index names (and therefore a distinct, isolated Spring + * context). Index names must be lowercase, which a lowercased UUID satisfies. + */ + @DynamicPropertySource + static void randomIndices(DynamicPropertyRegistry registry) { + String suffix = "_" + UUID.randomUUID().toString().toLowerCase(); + registry.add("elasticsearch.tree_node.index", () -> "it_tree" + suffix); + registry.add("elasticsearch.configuration_node.index", () -> "it_config" + suffix); + registry.add("elasticsearch.snapshot_node.index", () -> "it_snapshot" + suffix); + registry.add("elasticsearch.composite_snapshot_node.index", () -> "it_composite" + suffix); + registry.add("elasticsearch.filter.index", () -> "it_filter" + suffix); + } + + @Autowired + private ElasticsearchClient client; + + @Autowired + private Environment environment; + + @AfterAll + void dropIndices() throws Exception { + for (String key : INDEX_PROPERTY_KEYS) { + String index = environment.getProperty(key); + if (index != null) { + client.indices().delete(d -> d.index(index).ignoreUnavailable(true)); + } + } + } +} diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java index 7e289d2122..403051eafb 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/DAOTestIT.java @@ -18,10 +18,6 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; -import co.elastic.clients.elasticsearch.indices.ExistsRequest; -import co.elastic.clients.transport.endpoints.BooleanResponse; import org.epics.vtype.Alarm; import org.epics.vtype.AlarmSeverity; import org.epics.vtype.AlarmStatus; @@ -29,12 +25,9 @@ import org.epics.vtype.Time; import org.epics.vtype.VDouble; import org.epics.vtype.VInt; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.ConfigPv; @@ -49,19 +42,13 @@ import org.phoebus.applications.saveandrestore.model.TagData; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.applications.saveandrestore.model.search.SearchResult; +import org.phoebus.service.saveandrestore.AbstractElasticsearchIT; import org.phoebus.service.saveandrestore.NodeNotFoundException; -import org.phoebus.service.saveandrestore.persistence.config.ElasticConfig; import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchDAO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Profile; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -82,32 +69,12 @@ * Integration test to be executed against a running Elasticsearch 8.x instance. * It must be run with application property spring.profiles.active=IT. */ -@TestInstance(Lifecycle.PER_CLASS) -@SpringBootTest -@ContextConfiguration(classes = {ElasticConfig.class}) -@TestPropertySource(locations = "classpath:test_application.properties") -@Profile("IT") @SuppressWarnings("unused") -public class DAOTestIT { +public class DAOTestIT extends AbstractElasticsearchIT { @Autowired private ElasticsearchDAO nodeDAO; - @Autowired - private ElasticsearchClient client; - - @Value("${elasticsearch.tree_node.index:test_saveandrestore_configuration}") - private String ES_CONFIGURATION_INDEX; - - @Value("${elasticsearch.tree_node.index:test_saveandrestore_tree}") - private String ES_TREE_INDEX; - - @Value("${elasticsearch.filter.index:test_saveandrestore_filter}") - private String ES_FILTER_INDEX; - - @Value("${elasticsearch.composite_snapshot_node.index:test_saveandrestore_composite_snapshot}") - private String ES_COMPOSITE_SNAPSHOT_INDEX; - private static Alarm alarm; private static Time time; private static Display display; @@ -1846,9 +1813,6 @@ public void testGetAllNodes() { assertEquals(2, nodes.size()); } - /** - * Deletes all objects in all indices. - */ private void clearAllData() { List childNodes = nodeDAO.getChildNodes(Node.ROOT_FOLDER_UNIQUE_ID); childNodes.forEach(node -> nodeDAO.deleteNodes(List.of(node.getUniqueId()))); @@ -1856,41 +1820,6 @@ private void clearAllData() { } - @AfterAll - public void dropIndices() { - try { - BooleanResponse exists = client.indices().exists(ExistsRequest.of(e -> e.index(ES_CONFIGURATION_INDEX))); - if (exists.value()) { - client.indices().delete( - DeleteIndexRequest.of( - c -> c.index(ES_CONFIGURATION_INDEX))); - } - } catch (IOException e) { - e.printStackTrace(); - } - - try { - BooleanResponse exists = client.indices().exists(ExistsRequest.of(e -> e.index(ES_TREE_INDEX))); - if (exists.value()) { - client.indices().delete( - DeleteIndexRequest.of( - c -> c.index(ES_TREE_INDEX))); - } - } catch (IOException e) { - e.printStackTrace(); - } - - try { - BooleanResponse exists = client.indices().exists(ExistsRequest.of(e -> e.index(ES_COMPOSITE_SNAPSHOT_INDEX))); - if (exists.value()) { - client.indices().delete( - DeleteIndexRequest.of( - c -> c.index(ES_COMPOSITE_SNAPSHOT_INDEX))); - } - } catch (IOException e) { - e.printStackTrace(); - } - } @Test public void testCheckForPVNameDuplicates() { diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepositoryTestIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepositoryTestIT.java index e08ca199fb..b8902a493a 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepositoryTestIT.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ConfigurationDataRepositoryTestIT.java @@ -19,25 +19,13 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; -import co.elastic.clients.elasticsearch.indices.ExistsRequest; -import co.elastic.clients.transport.endpoints.BooleanResponse; -import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; import org.phoebus.applications.saveandrestore.model.ConfigPv; import org.phoebus.applications.saveandrestore.model.ConfigurationData; -import org.phoebus.service.saveandrestore.persistence.config.ElasticConfig; +import org.phoebus.service.saveandrestore.AbstractElasticsearchIT; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Profile; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; -import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -50,19 +38,8 @@ * Integration test to be executed against a running Elasticsearch 8.x instance. * It must be run with application property spring.profiles.active=IT. */ -@TestInstance(Lifecycle.PER_CLASS) -@SpringBootTest -@ContextConfiguration(classes = ElasticConfig.class) -@TestPropertySource(locations = "classpath:test_application.properties") -@Profile("IT") @SuppressWarnings("unused") -public class ConfigurationDataRepositoryTestIT { - - @Autowired - private ElasticsearchClient client; - - @Value("${elasticsearch.tree_node.index:test_saveandrestore_configuration}") - private String ES_CONFIGURATION_INDEX; +public class ConfigurationDataRepositoryTestIT extends AbstractElasticsearchIT { @Autowired private ConfigurationDataRepository configurationDataRepository; @@ -142,18 +119,9 @@ public void testDeleteAll(){ assertEquals(0, configurationDataRepository.count()); } - - @AfterAll - public void dropIndex() { - try { - BooleanResponse exists = client.indices().exists(ExistsRequest.of(e -> e.index(ES_CONFIGURATION_INDEX))); - if (exists.value()) { - client.indices().delete( - DeleteIndexRequest.of( - c -> c.index(ES_CONFIGURATION_INDEX))); - } - } catch (IOException e) { - e.printStackTrace(); - } + @AfterEach + public void cleanUp() { + configurationDataRepository.deleteAll(); } } + diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepositoryTestIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepositoryTestIT.java index e310984885..e54ebea902 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepositoryTestIT.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepositoryTestIT.java @@ -19,28 +19,16 @@ package org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; -import co.elastic.clients.elasticsearch.indices.ExistsRequest; -import co.elastic.clients.transport.endpoints.BooleanResponse; -import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.NodeType; +import org.phoebus.service.saveandrestore.AbstractElasticsearchIT; import org.phoebus.service.saveandrestore.NodeNotFoundException; import org.phoebus.service.saveandrestore.model.ESTreeNode; -import org.phoebus.service.saveandrestore.persistence.config.ElasticConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Profile; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import org.springframework.web.server.ResponseStatusException; -import java.io.IOException; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -60,19 +48,8 @@ * Integration test to be executed against a running Elasticsearch 8.x instance. * It must be run with application property spring.profiles.active=IT. */ -@TestInstance(Lifecycle.PER_CLASS) -@SpringBootTest -@ContextConfiguration(classes = ElasticConfig.class) -@TestPropertySource(locations = "classpath:test_application.properties") -@Profile("IT") @SuppressWarnings("unused") -public class ElasticsearchTreeRepositoryTestIT { - - @Autowired - private ElasticsearchClient client; - - @Value("${elasticsearch.tree_node.index:test_saveandrestore_tree}") - private String ES_TREE_INDEX; +public class ElasticsearchTreeRepositoryTestIT extends AbstractElasticsearchIT { @Autowired private ElasticsearchTreeRepository elasticsearchTreeRepository; @@ -232,17 +209,10 @@ public void testGetParentNodeMultipleParents() { () -> elasticsearchTreeRepository.getParentNode("aaaa")); } - @AfterAll - public void dropIndex() { - try { - BooleanResponse exists = client.indices().exists(ExistsRequest.of(e -> e.index(ES_TREE_INDEX))); - if (exists.value()) { - client.indices().delete( - DeleteIndexRequest.of( - c -> c.index(ES_TREE_INDEX))); - } - } catch (IOException e) { - e.printStackTrace(); - } + @AfterEach + public void cleanUp() { + elasticsearchTreeRepository.deleteAll(); } + } + diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java index 99f8a7298c..2b4afe25f8 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTest.java @@ -47,7 +47,7 @@ @ExtendWith(SpringExtension.class) @ContextHierarchy({@ContextConfiguration(classes = {ControllersTestConfig.class, WebSecurityConfig.class})}) @TestPropertySource(locations = "classpath:test_application.properties") -@WebMvcTest(NodeController.class) +@WebMvcTest(ComparisonController.class) public class ComparisonControllerTest { @Autowired diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTestIT.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTestIT.java index ee74fa88f0..180c8098e1 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTestIT.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/ComparisonControllerTestIT.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; import org.phoebus.applications.saveandrestore.model.ConfigPv; @@ -15,13 +14,9 @@ import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.model.Snapshot; import org.phoebus.applications.saveandrestore.model.SnapshotItem; -import org.phoebus.service.saveandrestore.persistence.config.ElasticConfig; +import org.phoebus.service.saveandrestore.AbstractElasticsearchIT; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Profile; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import java.util.ArrayList; import java.util.List; @@ -29,12 +24,7 @@ import static org.junit.jupiter.api.Assertions.*; -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@SpringBootTest -@ContextConfiguration(classes = ElasticConfig.class) -@TestPropertySource(locations = "classpath:test_application.properties") -@Profile("IT") -public class ComparisonControllerTestIT { +public class ComparisonControllerTestIT extends AbstractElasticsearchIT { @Autowired private ComparisonController comparisonController;