diff --git a/.github/workflows/examples-fresh-clone.yml b/.github/workflows/examples-fresh-clone.yml new file mode 100644 index 000000000..e4eb3185b --- /dev/null +++ b/.github/workflows/examples-fresh-clone.yml @@ -0,0 +1,124 @@ +name: Examples from a fresh clone + +# Builds each product's console examples as AGENTS.md says, from a clone +# with its submodules, against the packages published on Maven Central, with +# the JDK and Maven the runner has. Then runs the getting started example +# from the repository root with no settings. A fresh clone holds only a link +# for each free data file. On Ubuntu the example downloads its file, which +# tests the download, and the job keeps the file in the cache. The other +# systems take it from there, so each file is downloaded once a run. + +on: + pull_request: + paths: + - 'examples/**' + - 'cxx' + - '.github/workflows/examples-fresh-clone.yml' + - 'ci/run-fresh-clone-example.ps1' + push: + branches: + - main + - rc + schedule: + - cron: '30 4 * * *' + workflow_dispatch: + +permissions: + contents: read + +jobs: + download: + name: ${{ matrix.product }} on ubuntu-latest, downloading its data file + strategy: + fail-fast: false + matrix: + include: + - product: device-detection + example: fiftyone.devicedetection.examples.console.GettingStartedOnPrem + file: cxx/data/device-detection/51Degrees-LiteV4.1.hash + - product: ip-intelligence + example: fiftyone.ipintelligence.examples.console.GettingStartedOnPrem + file: cxx/data/ip-intelligence/51Degrees-LiteV41.ipi + runs-on: ubuntu-latest + steps: + - name: Clone the repository with its submodules + uses: actions/checkout@v4 + with: + submodules: recursive + persist-credentials: false + + - name: The runner's Java and Maven + shell: pwsh + run: | + java -version + mvn -v + + - name: Build the console examples against the published packages + working-directory: examples/${{ matrix.product }} + shell: pwsh + run: mvn -B --no-transfer-progress -pl console -am package -DskipTests + + - name: Run the getting started example from the repository root + shell: pwsh + run: >- + ./ci/run-fresh-clone-example.ps1 -Product '${{ matrix.product }}' + -Example '${{ matrix.example }}' -ExpectDownload $true + + # The key follows the link, so a new free file gets a new entry, and + # the same file is kept once rather than once a run. + - name: Keep the data file for the other systems + uses: actions/cache/save@v4 + with: + path: ${{ matrix.file }} + key: fresh-clone-${{ matrix.product }}-${{ hashFiles(format('{0}.link', matrix.file)) }} + enableCrossOsArchive: true + + other-systems: + name: ${{ matrix.product }} on ${{ matrix.os }} + needs: download + strategy: + fail-fast: false + matrix: + # macOS runners are shared by the whole organisation, so pull + # requests leave them out and the pushes and the daily run use them. + os: ${{ fromJSON(github.event_name == 'pull_request' && '["windows-latest"]' || '["windows-latest", "macos-latest"]') }} + product: [device-detection, ip-intelligence] + include: + - product: device-detection + example: fiftyone.devicedetection.examples.console.GettingStartedOnPrem + file: cxx/data/device-detection/51Degrees-LiteV4.1.hash + - product: ip-intelligence + example: fiftyone.ipintelligence.examples.console.GettingStartedOnPrem + file: cxx/data/ip-intelligence/51Degrees-LiteV41.ipi + runs-on: ${{ matrix.os }} + steps: + - name: Clone the repository with its submodules + uses: actions/checkout@v4 + with: + submodules: recursive + persist-credentials: false + + - name: Take the data file the Ubuntu job downloaded + uses: actions/cache/restore@v4 + with: + path: ${{ matrix.file }} + key: fresh-clone-${{ matrix.product }}-${{ hashFiles(format('{0}.link', matrix.file)) }} + enableCrossOsArchive: true + fail-on-cache-miss: true + + - name: The runner's Java and Maven + shell: pwsh + run: | + java -version + mvn -v + + - name: Build the console examples against the published packages + working-directory: examples/${{ matrix.product }} + shell: pwsh + run: mvn -B --no-transfer-progress -pl console -am package -DskipTests + + - name: Run the getting started example from the repository root + shell: pwsh + run: >- + ./ci/run-fresh-clone-example.ps1 -Product '${{ matrix.product }}' + -Example '${{ matrix.example }}' -ExpectDownload $false diff --git a/AGENTS.md b/AGENTS.md index e27e80b3a..6998a5278 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,9 @@ the native engines from `cxx` and does need them, as the README says. ### Prerequisites - Git. -- A JDK, 8 or later. CI tests 8, 11, 17 and 21 (`ci/options.json`). +- A JDK, 8 or later. CI tests 8, 11, 17 and 21 (`ci/options.json`), and + builds and runs the examples on the JDK each runner has (see + [What CI tests](#pitfalls)). - Maven 3.8 or later, or the `mvnw` wrapper at the root. - Network access to GitHub, to Maven Central for the packages the examples use, and to `distributor.51degrees.com` for the data files. @@ -215,12 +217,20 @@ example. The IP Intelligence cloud examples read their key from for Windows x64 and x86, Linux x64 and aarch64, and macOS x64 and aarch64. The `ip-intelligence.engine.on-premise` 4.5.128 jar has the same, without Windows x86. On a platform a jar lacks, the example cannot load the engine. -- **What CI does not test.** On a pull request CI builds and tests the - examples with the `dev` profile (`ci/build-project.ps1` and - `ci/run-unit-tests.ps1`), and against the published packages only when it - publishes (`ci/run-integration-tests.ps1`). It never runs the console - example jars as above, and the example tests use the paid data files when - CI has them. +- **What CI tests.** On a pull request CI builds and tests the examples + with the `dev` profile (`ci/build-project.ps1` and + `ci/run-unit-tests.ps1`), and the example tests use the paid data files + when CI has them. Publish tests them against the packages it has just + built (`ci/run-integration-tests.ps1`). + [examples-fresh-clone.yml](.github/workflows/examples-fresh-clone.yml) + does what [Run the examples](#run-the-examples) says, with the JDK and + Maven each runner has. It + builds the console examples against the published packages and runs each + product's `GettingStartedOnPrem` jar from the repository root with no + settings. On Ubuntu the free data file is downloaded, as from a fresh + clone, and the Windows and macOS jobs take that file from the run's cache, + so each file is downloaded once a run. It runs on each push to `main` and + `rc`, daily, and on a pull request that changes the examples. ## Environment variable names diff --git a/ci/README.md b/ci/README.md index 9b205e57f..e14c6793b 100644 --- a/ci/README.md +++ b/ci/README.md @@ -40,6 +40,14 @@ repositories used to do for one package tree each. of cxx. No test job downloads a data file, so a file added to the data repository needs an entry in common-ci's `steps/fetch-assets.ps1` before CI can use it. +- [run-fresh-clone-example.ps1](run-fresh-clone-example.ps1) runs a + product's console example jar from the repository root for + [examples-fresh-clone.yml](../.github/workflows/examples-fresh-clone.yml). + It fails unless the example exits with 0 and prints results, and unless it + downloads its free data file exactly when the job expects it to. That + workflow's Ubuntu job downloads each product's free data file once a run, + as a test of the download, and its other jobs take the file from the cache + the Ubuntu job saves. - [run-performance-tests.ps1](run-performance-tests.ps1) measures the Pipeline API, Device Detection and IP Intelligence and writes one results file per configuration. The Device Detection and IP Intelligence metrics are prefixed diff --git a/ci/run-fresh-clone-example.ps1 b/ci/run-fresh-clone-example.ps1 new file mode 100644 index 000000000..5c1e2102b --- /dev/null +++ b/ci/run-fresh-clone-example.ps1 @@ -0,0 +1,44 @@ +param ( + [Parameter(Mandatory)][string]$Product, + [Parameter(Mandatory)][string]$Example, + # True where the clone holds only the data file's link, so the example + # has to download the file. False where the file is already in place. + [Parameter(Mandatory)][bool]$ExpectDownload +) + +# Runs a product's console example jar from the repository root with no +# settings, as AGENTS.md's "Run the examples" does, and fails unless it ends +# with exit code 0, prints results, and downloads its data file exactly when +# expected. + +$jar = Get-ChildItem "examples/$Product/console/target" ` + -Filter '*-jar-with-dependencies.jar' | Select-Object -First 1 +if ($null -eq $jar) { + Write-Output '::error::The build made no jar with dependencies.' + exit 1 +} +$output = & java -cp $jar.FullName $Example 2>&1 | ForEach-Object { "$_" } +$code = $LASTEXITCODE +$output +$text = $output -join "`n" +$failed = $false +if ($code -ne 0) { + Write-Output "::error::The example exited with $code." + $failed = $true +} +$downloaded = $text -match 'is missing, so it is being downloaded' +if ($ExpectDownload -and -not $downloaded) { + Write-Output '::error::The example did not download its free data file.' + $failed = $true +} +if (-not $ExpectDownload -and $downloaded) { + Write-Output '::error::The example downloaded its data file, which should have come from the cache.' + $failed = $true +} +if ($text -notmatch 'Results:') { + Write-Output '::error::The example printed no results.' + $failed = $true +} +if ($failed) { + exit 1 +} diff --git a/examples/device-detection/pom.xml b/examples/device-detection/pom.xml index 8e9dd78bc..05c6fe34a 100644 --- a/examples/device-detection/pom.xml +++ b/examples/device-detection/pom.xml @@ -62,7 +62,7 @@ 2.0.20 1.3.16 - 3.26.0 + 3.28.0 diff --git a/examples/ip-intelligence/pom.xml b/examples/ip-intelligence/pom.xml index 8d7ff14cf..585aaa426 100644 --- a/examples/ip-intelligence/pom.xml +++ b/examples/ip-intelligence/pom.xml @@ -63,7 +63,7 @@ 2.0.20 1.3.16 - 3.26.0 + 3.28.0