eckit-2.3.0/0000775000175000017500000000000015253173651013007 5ustar alastairalastaireckit-2.3.0/project_summary.cmake0000664000175000017500000000102015253173651017225 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 if( MPI_C_FOUND ) message( STATUS "MPI") message( STATUS " MPI_C_INCLUDE_PATH : [${MPI_C_INCLUDE_PATH}]") message( STATUS " MPI_C_LIBRARIES : [${MPI_C_LIBRARIES}]") message( STATUS " MPIEXEC : [${MPIEXEC}]") endif() if(CURSES_FOUND) message( STATUS " Curses includes : [${CURSES_INCLUDE_DIR}]" ) message( STATUS " libs : [${CURSES_LIBRARIES}]" ) endif() eckit-2.3.0/.pre-commit-config.yaml0000664000175000017500000000461315253173651017274 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: mixed-line-ending args: [--fix=lf] - id: check-merge-conflict - id: pretty-format-json args: [--autofix] types: [json] exclude: '\.geojson$' - repo: https://github.com/MarcoGorelli/cython-lint rev: v0.16.6 hooks: - id: cython-lint types: [cython] - id: double-quote-cython-strings types: [cython] - repo: https://github.com/pre-commit/mirrors-clang-format rev: v19.1.7 hooks: - id: clang-format types_or: [c, c++] exclude: 'src/_eckit/_.*\.(h|cpp)' - repo: https://github.com/ecmwf/ci-infrastructure rev: dac4b18ffe60c317fbab05246e851b6971f7eabf hooks: - id: check-ci-approval # Off until the tree is cleaned up; the counts are what each would flag today. # # - repo: https://github.com/pre-commit/pre-commit-hooks # rev: v5.0.0 # hooks: # - id: trailing-whitespace # 6 files # exclude: '^LICENSES/' # canonical licence texts are never edited # - id: end-of-file-fixer # 10 files # exclude: '^LICENSES/' # # - repo: https://github.com/PyCQA/isort # rev: 6.0.1 # hooks: # - id: isort # 4 files # types_or: [python, cython] # args: [--line-length=110, --force-single-line-imports, --profile=black] # # - repo: https://github.com/astral-sh/ruff-pre-commit # rev: v0.9.8 # hooks: # - id: ruff # 2 errors, src/sandbox/server.py # types: [python] # exclude: '_\.py$' # args: [--line-length=110, --fix, --exit-non-zero-on-fix, --preview] # # - repo: https://github.com/adrienverge/yamllint.git # rev: v1.29.0 # hooks: # - id: yamllint # 44 files # args: [--strict, -d, '{extends: default, rules: {line-length: disable}}'] # types: [yaml] # `reuse lint` checks the whole project. # The hook is passed no filenames, so it cannot be narrowed by an exclude; # always_run keeps it honest for a commit that happens to touch nothing it would otherwise see. - repo: https://github.com/fsfe/reuse-tool rev: v6.2.0 hooks: - id: reuse always_run: true eckit-2.3.0/.github/0000775000175000017500000000000015253173651014347 5ustar alastairalastaireckit-2.3.0/.github/ci-hpc-config.yml0000664000175000017500000000066715253173651017511 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 matrix: - mpi_on - mpi_off mpi_on: build: modules: - ninja modules_package: - eckit:openmpi dependencies: - ecmwf/ecbuild@develop parallel: 64 ntasks: 4 mpi_off: build: modules: - ninja dependencies: - ecmwf/ecbuild@develop parallel: 64 eckit-2.3.0/.github/ci-approval-allowlist.yml0000664000175000017500000000062515253173651021322 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 --- exempt: - workflow: contributor-declaration.yml - workflow: label-public-pr.yml - workflow: notify-pull-request.yml - workflow: pr-label-downstream-ci.yml - workflow: old_CI.yml reason: "legacy, retiring; gates downstream-ci with an if: rather than needs:" eckit-2.3.0/.github/ci-config.yml0000664000175000017500000000041215253173651016725 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 dependencies: | ecmwf/ecbuild Deutsches-Klimarechenzentrum/libaec@refs/tags/v1.1.6 dependency_branch: develop parallelism_factor: 8 eckit-2.3.0/.github/actionlint.yaml0000664000175000017500000000111315253173651017373 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # # SPDX-License-Identifier: Apache-2.0 # actionlint validates runs-on against GitHub's hosted labels and flags anything # else as unknown. These are this org's ARC scale sets and the HPC login-node # runner; add new ones here when a scale set is added. Kept identical to # ecmwf/ci-infrastructure/.github/actionlint.yaml so the files stay diffable. self-hosted-runner: labels: - arc-runner-normal - arc-runner-large - arc-runner-very-large - arc-hpc-pet-vsphere-prod - hpc eckit-2.3.0/.github/workflows/0000775000175000017500000000000015253173651016404 5ustar alastairalastaireckit-2.3.0/.github/workflows/contributor-declaration.yml0000664000175000017500000000141715253173651023767 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Contributor Declaration on: # Parked: run it by hand only, until the declaration check comes back. To # restore, put back the trigger below -- pull_request_target runs the BASE # branch's copy of this file, so a pull request cannot edit the gate that # judges it, and it is safe here because nothing from the pull request is ever # checked out or executed and no write token is used. # # pull_request_target: # types: [opened, edited, reopened, synchronize] workflow_dispatch: permissions: contents: read jobs: contributor-declaration: uses: ecmwf/ci-infrastructure/.github/workflows/check-pr-declaration.yml@main eckit-2.3.0/.github/workflows/trigger-downstream.yml0000664000175000017500000001156315253173651022761 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # GENERATED FILE - DO NOT EDIT. # Regenerate via the ci-infrastructure-generate CLI. # Source of truth: each repo's .ci/manifest.toml. name: Downstream runner (eckit) on: workflow_run: workflows: - CI types: - completed concurrency: group: trigger-downstream-runner-${{ github.event.workflow_run.head_sha }} cancel-in-progress: true jobs: context: runs-on: ubuntu-slim permissions: actions: read outputs: head-sha: ${{ steps.ctx.outputs.head-sha }} head-branch: ${{ steps.ctx.outputs.head-branch }} ci-conclusion: ${{ steps.ctx.outputs.ci-conclusion }} ci-url: ${{ steps.ctx.outputs.ci-url }} ci-summary: ${{ steps.ctx.outputs.ci-summary }} steps: - name: Resolve the commit under test id: ctx uses: ecmwf/ci-infrastructure/actions/resolve-dispatch-context@main label-gate: runs-on: ubuntu-slim permissions: pull-requests: read outputs: run: ${{ steps.gate.outputs.run }} steps: - name: Check the downstream-CI label id: gate uses: ecmwf/ci-infrastructure/actions/check-pr-label@main with: label: run-downstream-CI sha: ${{ needs.context.outputs.head-sha }} needs: - context validate: if: ${{ (needs.context.outputs.ci-conclusion == 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim steps: - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - uses: actions/checkout@v6 with: ref: ${{ needs.context.outputs.head-sha }} token: ${{ steps.mint.outputs.token }} allow-unsafe-pr-checkout: true - uses: ecmwf/ci-infrastructure/actions/validate-generated-workflows@main with: token: ${{ steps.mint.outputs.token }} needs: - context - label-gate report-start: if: ${{ (needs.context.outputs.ci-conclusion == 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim permissions: statuses: write steps: - name: Post pending downstream status env: GH_TOKEN: ${{ github.token }} run: | gh api -X POST \ "/repos/${{ github.repository }}/statuses/${{ needs.context.outputs.head-sha }}" \ -f state=pending \ -f context='downstream/runner' \ -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ -f description='Downstream runner tests running' needs: - context - label-gate report-ci-failure: if: ${{ (needs.context.outputs.ci-conclusion != 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim permissions: statuses: write steps: - name: Post downstream failure status env: GH_TOKEN: ${{ github.token }} run: | gh api -X POST \ "/repos/${{ github.repository }}/statuses/${{ needs.context.outputs.head-sha }}" \ -f state=failure \ -f context='downstream/runner' \ -f target_url="${{ needs.context.outputs.ci-url }}" \ -f description="${{ needs.context.outputs.ci-summary }}; downstream runner not run" needs: - context - label-gate eccodes: name: eccodes needs: - context - label-gate - validate uses: ecmwf/eccodes/.github/workflows/cross-repo-trigger.yml@develop with: from-repo: ${{ github.repository }} from-sha: ${{ needs.context.outputs.head-sha }} from-jobs: '["eckit/build"]' branch: ${{ needs.context.outputs.head-branch }} fallback-ref: develop secrets: inherit if: ${{ needs['label-gate'].outputs.run == 'true' }} report-result: needs: - context - label-gate - validate - eccodes if: ${{ (always() && needs.context.outputs.ci-conclusion == 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim permissions: statuses: write steps: - name: Post final downstream status env: GH_TOKEN: ${{ github.token }} run: | state=success for r in ${{ needs.validate.result }} ${{ needs.eccodes.result }}; do if [ "$r" = failure ] || [ "$r" = cancelled ]; then state=failure fi done gh api -X POST \ "/repos/${{ github.repository }}/statuses/${{ needs.context.outputs.head-sha }}" \ -f state="$state" \ -f context='downstream/runner' \ -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ -f description="Downstream runner tests $state" eckit-2.3.0/.github/workflows/trigger-downstream-hpc.yml0000664000175000017500000001154315253173651023527 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # GENERATED FILE - DO NOT EDIT. # Regenerate via the ci-infrastructure-generate CLI. # Source of truth: each repo's .ci/manifest.toml. name: Downstream HPC (eckit) on: workflow_run: workflows: - CI types: - completed concurrency: group: trigger-downstream-hpc-${{ github.event.workflow_run.head_sha }} cancel-in-progress: true jobs: context: runs-on: ubuntu-slim permissions: actions: read outputs: head-sha: ${{ steps.ctx.outputs.head-sha }} head-branch: ${{ steps.ctx.outputs.head-branch }} ci-conclusion: ${{ steps.ctx.outputs.ci-conclusion }} ci-url: ${{ steps.ctx.outputs.ci-url }} ci-summary: ${{ steps.ctx.outputs.ci-summary }} steps: - name: Resolve the commit under test id: ctx uses: ecmwf/ci-infrastructure/actions/resolve-dispatch-context@main label-gate: runs-on: ubuntu-slim permissions: pull-requests: read outputs: run: ${{ steps.gate.outputs.run }} steps: - name: Check the downstream-CI label id: gate uses: ecmwf/ci-infrastructure/actions/check-pr-label@main with: label: run-downstream-CI sha: ${{ needs.context.outputs.head-sha }} needs: - context validate: if: ${{ (needs.context.outputs.ci-conclusion == 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim steps: - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - uses: actions/checkout@v6 with: ref: ${{ needs.context.outputs.head-sha }} token: ${{ steps.mint.outputs.token }} allow-unsafe-pr-checkout: true - uses: ecmwf/ci-infrastructure/actions/validate-generated-workflows@main with: token: ${{ steps.mint.outputs.token }} needs: - context - label-gate report-start: if: ${{ (needs.context.outputs.ci-conclusion == 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim permissions: statuses: write steps: - name: Post pending downstream status env: GH_TOKEN: ${{ github.token }} run: | gh api -X POST \ "/repos/${{ github.repository }}/statuses/${{ needs.context.outputs.head-sha }}" \ -f state=pending \ -f context='downstream/hpc' \ -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ -f description='Downstream HPC tests running' needs: - context - label-gate report-ci-failure: if: ${{ (needs.context.outputs.ci-conclusion != 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim permissions: statuses: write steps: - name: Post downstream failure status env: GH_TOKEN: ${{ github.token }} run: | gh api -X POST \ "/repos/${{ github.repository }}/statuses/${{ needs.context.outputs.head-sha }}" \ -f state=failure \ -f context='downstream/hpc' \ -f target_url="${{ needs.context.outputs.ci-url }}" \ -f description="${{ needs.context.outputs.ci-summary }}; downstream HPC not run" needs: - context - label-gate eccodes: name: eccodes needs: - context - label-gate - validate uses: ecmwf/eccodes/.github/workflows/cross-repo-trigger-hpc.yml@develop with: from-repo: ${{ github.repository }} from-sha: ${{ needs.context.outputs.head-sha }} from-jobs: '["eckit/build-hpc"]' branch: ${{ needs.context.outputs.head-branch }} fallback-ref: develop secrets: inherit if: ${{ needs['label-gate'].outputs.run == 'true' }} report-result: needs: - context - label-gate - validate - eccodes if: ${{ (always() && needs.context.outputs.ci-conclusion == 'success') && needs['label-gate'].outputs.run == 'true' }} runs-on: ubuntu-slim permissions: statuses: write steps: - name: Post final downstream status env: GH_TOKEN: ${{ github.token }} run: | state=success for r in ${{ needs.validate.result }} ${{ needs.eccodes.result }}; do if [ "$r" = failure ] || [ "$r" = cancelled ]; then state=failure fi done gh api -X POST \ "/repos/${{ github.repository }}/statuses/${{ needs.context.outputs.head-sha }}" \ -f state="$state" \ -f context='downstream/hpc' \ -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ -f description="Downstream HPC tests $state" eckit-2.3.0/.github/workflows/pr-label-downstream-ci.yml0000664000175000017500000000170515253173651023402 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: "[PR] Downstream-CI label gate" on: # pull_request_target runs the BASE branch's copy of this file and gets a token # that can post a status on a fork's pull request. Safe here because nothing # from the pull request is ever checked out or executed. # # `labeled`/`unlabeled` are what make the gate reactive; `synchronize` re-posts # it, since a commit status is per-SHA. pull_request_target: types: [opened, reopened, synchronize, labeled, unlabeled] permissions: statuses: write jobs: gate: runs-on: ubuntu-slim steps: - uses: ecmwf/ci-infrastructure/actions/require-label-decision@main with: label: run-downstream-CI opt-out-label: downstream-CI-not-needed context: downstream-ci-label description-decided: Downstream-CI decision recorded eckit-2.3.0/.github/workflows/notify-issues.yml0000664000175000017500000000062315253173651021751 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Notify Issues on: issues: types: [opened, reopened] jobs: notify: runs-on: ubuntu-latest steps: - name: Notify Issues uses: ecmwf/notify-teams-issue@v1 with: incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} eckit-2.3.0/.github/workflows/ci.yml0000664000175000017500000001500115253173651017517 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: CI on: push: branches: [develop, master] pull_request: types: [opened, reopened, synchronize, labeled] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: ARTIFACT_POLL_INTERVAL: ${{ vars.ARTIFACT_POLL_INTERVAL || '60' }} ARTIFACT_S3_ENDPOINT: ${{ secrets.ARTIFACT_S3_ENDPOINT }} ARTIFACT_S3_BUCKET: ${{ secrets.ARTIFACT_S3_BUCKET }} SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} permissions: contents: read jobs: ci-approval: runs-on: ubuntu-slim permissions: pull-requests: write steps: - uses: ecmwf/ci-infrastructure/actions/require-ci-approval@main pre-commit: needs: ci-approval runs-on: ubuntu-slim env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' steps: - uses: actions/checkout@v6 - uses: ecmwf/ci-infrastructure/actions/pre-commit@main resolve: needs: [ci-approval, pre-commit] runs-on: arc-runner-normal container: image: eccr.ecmwf.int/public-ci-images/ubuntu24.04-base:latest outputs: build-matrix: ${{ steps.r.outputs.matrix-build }} build-hpc-matrix: ${{ steps.r.outputs.matrix-build-hpc }} steps: - name: Announce image uses: ecmwf/ci-infrastructure/actions/announce-image@main - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - uses: ecmwf/ci-infrastructure/actions/checkout-under-test@main - id: r uses: ecmwf/ci-infrastructure/actions/resolve-deps@main with: current-branch: ${{ github.head_ref || github.ref_name }} matrix: build,build-hpc token: ${{ steps.mint.outputs.token }} client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} app-private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} build: needs: [ci-approval, resolve] name: build+test (${{ matrix._resolved['job-name'] }}) runs-on: ${{ matrix['runs-on'] }} container: image: ${{ matrix.container || '' }} strategy: fail-fast: false matrix: ${{ fromJSON(needs.resolve.outputs.build-matrix) }} steps: - name: Announce image uses: ecmwf/ci-infrastructure/actions/announce-image@main - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - uses: ecmwf/ci-infrastructure/actions/checkout-under-test@main - name: Fetch resolved deps id: deps uses: ecmwf/ci-infrastructure/actions/fetch-deps@main with: deps-json: ${{ toJSON(matrix._resolved.deps) }} token: ${{ steps.mint.outputs.token }} - name: Build eckit id: build uses: ./.github/actions/build-eckit with: cmake-prefix-path: ${{ steps.deps.outputs.cmake-prefix-path }} cxx-compiler: ${{ matrix.cxx-compiler }} build-type: ${{ matrix.build-type }} # Invocation comes from .ci/manifest.toml's [matrix.build], the same place # the generated cross-repo-trigger.yml renders its Test step from. Spelling # it out again here is how the two lanes drift apart. - name: Test if: ${{ matrix._resolved.ctest }} run: ctest --test-dir "${{ steps.build.outputs.build-dir }}" --output-on-failure ${{ matrix._resolved['ctest-args'] }} - name: Publish eckit uses: ecmwf/ci-infrastructure/actions/publish-artifact@main with: install-path: ${{ steps.build.outputs.install-path }} artifact-name: ${{ matrix._resolved.own-artifact-name }} - name: Print dependency table if: always() uses: ecmwf/ci-infrastructure/actions/print-dep-table@main with: resolved: ${{ toJSON(matrix._resolved) }} build-hpc: needs: [ci-approval, resolve] name: build-hpc (${{ matrix._resolved['job-name'] }}) runs-on: ${{ matrix['runs-on'] }} container: image: ${{ matrix.container }} credentials: username: ${{ secrets.ECCR_PULL_ROBOT_NAME }} password: ${{ secrets.ECCR_PULL_ROBOT_TOKEN }} strategy: fail-fast: false matrix: ${{ fromJSON(needs.resolve.outputs.build-hpc-matrix) }} steps: - name: Announce image uses: ecmwf/ci-infrastructure/actions/announce-image@main - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - uses: ecmwf/ci-infrastructure/actions/checkout-under-test@main - name: Fetch resolved deps id: deps uses: ecmwf/ci-infrastructure/actions/fetch-deps@main with: deps-json: ${{ toJSON(matrix._resolved.deps) }} token: ${{ steps.mint.outputs.token }} # Stage the dep prefixes for shipment without installing anything # here: they are consumed on the compute node, not in this container. install-python-deps: 'false' - name: Build eckit on HPC id: build uses: ecmwf/ci-infrastructure/actions/build-on-hpc@main with: site: ${{ matrix.site }} job-script: ${{ matrix.job-script || './.ci/hpc/build.sh.j2' }} # The render context for the .j2 recipe: the toolchain this leg declares. matrix-leg: ${{ toJSON(matrix) }} artifact-name: ${{ matrix._resolved.own-artifact-name }} cmake-prefix-path: ${{ steps.deps.outputs.cmake-prefix-path }} work-dir: ${{ vars.HPC_CI_WORK_DIR }} remote-work-dir: ${{ vars.HPC_CI_REMOTE_WORK_DIR }} troika-user: ${{ secrets.HPC_CI_SSH_USER }} - name: Print dependency table if: always() uses: ecmwf/ci-infrastructure/actions/print-dep-table@main with: resolved: ${{ toJSON(matrix._resolved) }} own-source: ${{ steps.build.outputs.cache-hit == 'true' && 'artifact' || 'built' }} eckit-2.3.0/.github/workflows/notify-pull-request.yml0000664000175000017500000000065115253173651023101 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Notify Pull Request on: pull_request_target: types: [opened, reopened] jobs: notify: runs-on: ubuntu-latest steps: - name: Notify Pull Request uses: ecmwf/notify-teams-pr@v1 with: incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} eckit-2.3.0/.github/workflows/old_CI.yml0000664000175000017500000001061315253173651020261 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: old CI on: # Trigger the workflow on push to master or develop, except tag creation push: branches: - "master" - "develop" tags-ignore: - "**" # Trigger the workflow on pull request pull_request: types: [opened, synchronize, reopened, ready_for_review] # Trigger on public pull request approval pull_request_target: types: [labeled] # Trigger the workflow manually workflow_dispatch: ~ jobs: # Run CI including downstream packages on self-hosted runners downstream-ci: name: downstream-ci if: ${{ !github.event.pull_request.draft && (!github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} uses: ecmwf/downstream-ci/.github/workflows/downstream-ci.yml@main with: eckit: ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }} codecov_upload: true clang_format: true clang_format_ignore: | src/eckit/contrib secrets: inherit # Run Rust CI (fmt + clippy + doc + test) on the rust/ workspace ci-rust: name: ci-rust if: ${{ !github.event.pull_request.draft && (!github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} uses: ecmwf/reusable-workflows/.github/workflows/ci-rust.yml@main with: manifest-path: rust/Cargo.toml features: --features vendored secrets: private_repos_token: ${{ secrets.GH_REPO_READ_TOKEN }} # Run CI of private downstream packages on self-hosted runners private-downstream-ci: name: private-downstream-ci needs: [downstream-ci] if: ${{ !github.event.pull_request.draft && (success() || failure()) && (!github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: Dispatch private downstream CI uses: ecmwf/dispatch-private-downstream-ci@v1 with: token: ${{ secrets.GH_REPO_READ_TOKEN }} owner: ecmwf repository: private-downstream-ci event_type: downstream-ci payload: '{"eckit": "ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }}"}' # Build downstream packages on HPC downstream-ci-hpc: name: downstream-ci-hpc if: ${{ !github.event.pull_request.draft && (!github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} uses: ecmwf/downstream-ci/.github/workflows/downstream-ci-hpc.yml@main with: eckit: ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }} secrets: inherit # Run CI of private downstream packages on HPC private-downstream-ci-hpc: name: private-downstream-ci-hpc needs: [downstream-ci-hpc] if: ${{ !github.event.pull_request.draft && (success() || failure()) && (!github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: Dispatch private downstream CI uses: ecmwf/dispatch-private-downstream-ci@v1 with: token: ${{ secrets.GH_REPO_READ_TOKEN }} owner: ecmwf repository: private-downstream-ci event_type: downstream-ci-hpc payload: '{"eckit": "ecmwf/eckit@${{ github.event.pull_request.head.sha || github.sha }}"}' notify: runs-on: ubuntu-latest needs: - downstream-ci - ci-rust - private-downstream-ci - downstream-ci-hpc - private-downstream-ci-hpc if: ${{ !github.event.pull_request.draft && always() && !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }} steps: - name: Trigger Teams notification if: failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') uses: ecmwf/notify-teams@v1 with: incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} needs_context: ${{ toJSON(needs) }} notify_on: | failure eckit-2.3.0/.github/workflows/check-release-version.yml0000664000175000017500000000051115253173651023302 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Check VERSION file on: push: branches: - "release/**" - "hotfix/**" jobs: check_version: uses: ecmwf/reusable-workflows/.github/workflows/check-release-version.yml@v2 eckit-2.3.0/.github/workflows/docs.yml0000664000175000017500000000524715253173651020067 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Publish Documentation to sites.ecmwf.int on: pull_request: types: [opened, synchronize, reopened, closed] push: branches: - 'develop' - 'master' tags: - '**' jobs: build: if: ${{ github.event_name == 'pull_request' && github.event.action != 'closed' || github.event_name == 'push'}} runs-on: ubuntu-latest outputs: artifact-id: ${{ steps.upload-doc.outputs.artifact-id }} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install python dependencies run: | python -m pip install -r docs/requirements.txt - name: Prepare Doxygen uses: ecmwf/reusable-workflows/setup-doxygen@main with: version: 1.14.0 - name: Build doc run: | cd docs make -j html - name: Archive documentation id: upload-doc uses: actions/upload-artifact@v4 with: name: documentation path: docs/_build/html preview-publish: if: ${{ github.event_name == 'pull_request' && github.event.action != 'closed' }} needs: build uses: ecmwf/reusable-workflows/.github/workflows/pr-preview-publish.yml@main with: artifact-id: ${{ needs.build.outputs.artifact-id }} space: docs name: dev-section path: eckit/pull-requests link-text: 🌦� >> Documentation << 🌦� secrets: sites-token: ${{ secrets.ECMWF_SITES_DOCS_DEV_SECTION_TOKEN }} preview-unpublish: if: ${{ github.event_name == 'pull_request' && github.event.action == 'closed' }} uses: ecmwf/reusable-workflows/.github/workflows/pr-preview-unpublish.yml@main with: space: docs name: dev-section path: eckit/pull-requests secrets: sites-token: ${{ secrets.ECMWF_SITES_DOCS_DEV_SECTION_TOKEN }} publish: if: >- ${{ github.event_name == 'push' && ( github.ref_name == 'develop' || github.ref_name == 'master' || github.ref_type == 'tag' ) }} needs: build uses: ecmwf/reusable-workflows/.github/workflows/docs-publish.yml@main with: artifact-id: ${{ needs.build.outputs.artifact-id }} space: docs name: dev-section path: eckit id: ${{ github.ref_name }} softlink: ${{ github.ref_name == 'master' && 'stable' || github.ref_name == 'develop' && 'latest' }} secrets: sites-token: ${{ secrets.ECMWF_SITES_DOCS_DEV_SECTION_TOKEN }} eckit-2.3.0/.github/workflows/reuse.yml0000664000175000017500000000124115253173651020250 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # Third-party actions are pinned to an immutable commit SHA rather than a tag, # per the Codex audit guidance. The tag beside each is a hint for locating the # release, not what is resolved. name: REUSE Compliance on: push: branches: [develop, master] pull_request: workflow_dispatch: permissions: contents: read jobs: reuse: runs-on: ubuntu-latest steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 eckit-2.3.0/.github/workflows/cross-repo-trigger-hpc.yml0000664000175000017500000002070215253173651023435 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # GENERATED FILE - DO NOT EDIT. # Regenerate via the ci-infrastructure-generate CLI. # Source of truth: each repo's .ci/manifest.toml. name: Cross-repo trigger (eckit) run-name: Cross-repo trigger (${{ inputs.from-repo }}@${{ inputs.from-sha }}) [${{ inputs.dispatch-id }}] on: workflow_call: inputs: from-repo: description: owner/repo of the dispatcher (upstream producer or downstream consumer) required: true type: string from-sha: description: SHA of the dispatcher commit required: true type: string from-jobs: description: JSON array of package/kinds that triggered us (e.g. '["fortmath/build","fortmath/build-hpc"]') for upstream-change dispatches. '[]' when rebuild-request is true. required: false default: '[]' type: string rebuild-request: description: True when a downstream consumer dispatches us because our artifact was missing. Mutually exclusive with from-jobs. required: false default: false type: boolean branch: description: Dispatcher branch name; we attempt branch-matching against it required: true type: string fallback-ref: description: Ref to check out if branch does not exist in this repo required: false type: string default: main workflow_dispatch: inputs: dispatch-id: description: Correlation id stamped into run-name so the dispatcher can find this run required: false default: '' type: string from-repo: description: owner/repo of the dispatcher (upstream producer or downstream consumer) required: true type: string from-sha: description: SHA of the dispatcher commit required: true type: string from-jobs: description: JSON array of package/kinds that triggered us (e.g. '["fortmath/build","fortmath/build-hpc"]') for upstream-change dispatches. '[]' when rebuild-request is true. required: false default: '[]' type: string rebuild-request: description: True when a downstream consumer dispatches us because our artifact was missing. Mutually exclusive with from-jobs. required: false default: false type: boolean branch: description: Dispatcher branch name; we attempt branch-matching against it required: true type: string fallback-ref: description: Ref to check out if branch does not exist in this repo required: false type: string default: main concurrency: group: cross-repo-trigger-hpc-eckit-${{ github.ref }} cancel-in-progress: false env: ARTIFACT_POLL_INTERVAL: ${{ vars.ARTIFACT_POLL_INTERVAL || '60' }} ARTIFACT_S3_ENDPOINT: ${{ secrets.ARTIFACT_S3_ENDPOINT }} ARTIFACT_S3_BUCKET: ${{ secrets.ARTIFACT_S3_BUCKET }} SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} jobs: resolve: if: contains(fromJSON(inputs.from-jobs), 'ecbuild/build-hpc') || contains(fromJSON(inputs.from-jobs), 'stack-deps/build-hpc') || inputs.rebuild-request runs-on: ubuntu-slim outputs: ref: ${{ steps.pick.outputs.ref }} matrix-build-hpc: ${{ steps.r.outputs.matrix-build-hpc }} steps: - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - id: pick uses: ecmwf/ci-infrastructure/actions/pick-ref@main with: repo: ecmwf/eckit try-branch: ${{ inputs.branch }} fallback-ref: ${{ inputs.fallback-ref }} token: ${{ steps.mint.outputs.token }} - uses: actions/checkout@v6 with: repository: ecmwf/eckit ref: ${{ steps.pick.outputs.ref }} token: ${{ steps.mint.outputs.token }} - id: r uses: ecmwf/ci-infrastructure/actions/resolve-deps@main with: current-branch: ${{ steps.pick.outputs.ref }} matrix: build-hpc token: ${{ steps.mint.outputs.token }} client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} app-private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} eckit__build_hpc: needs: - resolve if: contains(fromJSON(inputs.from-jobs), 'ecbuild/build-hpc') || contains(fromJSON(inputs.from-jobs), 'stack-deps/build-hpc') || inputs.rebuild-request name: eckit/build-hpc (${{ matrix._resolved['job-name'] }}) runs-on: ${{ matrix['runs-on'] }} container: image: ${{ matrix.container || '' }} credentials: username: ${{ secrets.ECCR_PULL_ROBOT_NAME }} password: ${{ secrets.ECCR_PULL_ROBOT_TOKEN }} strategy: fail-fast: false matrix: ${{ fromJSON(needs.resolve.outputs.matrix-build-hpc) }} steps: - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - id: check_start if: ${{ github.event_name == 'workflow_dispatch' && inputs.from-jobs != '[]' }} uses: ecmwf/ci-infrastructure/actions/report-check-run@main with: token: ${{ steps.mint.outputs.token }} head-repo: ${{ inputs.from-repo }} head-sha: ${{ inputs.from-sha }} name: eckit/build-hpc (${{ matrix._resolved['job-name'] }}) details-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} phase: start - name: Announce image uses: ecmwf/ci-infrastructure/actions/announce-image@main - uses: actions/checkout@v6 with: repository: ecmwf/eckit ref: ${{ needs.resolve.outputs.ref }} token: ${{ steps.mint.outputs.token }} - name: Decode matrix-leg id: m shell: bash env: MATRIX_LEG: ${{ toJSON(matrix) }} run: | set -euo pipefail shopt -s inherit_errexit 2>/dev/null || true command -v jq >/dev/null || { echo "::error::jq is required but not installed in this runner/image." >&2 exit 1 } leg="$MATRIX_LEG" require() { local val val=$(jq -r "$1" <<<"$leg") if [ -z "$val" ] || [ "$val" = "null" ]; then echo "::error::matrix-leg field '$2' empty or null (jq -r '$1' returned '$val')." >&2 exit 1 fi printf '%s' "$val" } deps_json=$(require '._resolved.deps | tojson' '_resolved.deps') own_artifact_name=$(require '._resolved."own-artifact-name"' '_resolved.own-artifact-name') { echo "deps-json=${deps_json}" echo "own-artifact-name=${own_artifact_name}" } >> "$GITHUB_OUTPUT" - name: Fetch resolved deps id: deps uses: ecmwf/ci-infrastructure/actions/fetch-deps@main with: deps-json: ${{ steps.m.outputs.deps-json }} token: ${{ steps.mint.outputs.token }} install-python-deps: 'false' - name: Build on HPC id: build uses: ecmwf/ci-infrastructure/actions/build-on-hpc@main with: site: ${{ matrix.site }} job-script: ${{ matrix.job-script || './.ci/hpc/build.sh.j2' }} matrix-leg: ${{ toJSON(matrix) }} artifact-name: ${{ steps.m.outputs.own-artifact-name }} cmake-prefix-path: ${{ steps.deps.outputs.cmake-prefix-path }} work-dir: ${{ vars.HPC_CI_WORK_DIR }} remote-work-dir: ${{ vars.HPC_CI_REMOTE_WORK_DIR }} troika-user: ${{ secrets.HPC_CI_SSH_USER }} - name: Report check-run conclusion if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.from-jobs != '[]' }} uses: ecmwf/ci-infrastructure/actions/report-check-run@main with: token: ${{ steps.mint.outputs.token }} head-repo: ${{ inputs.from-repo }} head-sha: ${{ inputs.from-sha }} name: eckit/build-hpc (${{ matrix._resolved['job-name'] }}) details-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} phase: finish conclusion: ${{ job.status }} check-run-id: ${{ steps.check_start.outputs.check-run-id }} eckit-2.3.0/.github/workflows/cross-repo-trigger.yml0000664000175000017500000002077215253173651022674 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # GENERATED FILE - DO NOT EDIT. # Regenerate via the ci-infrastructure-generate CLI. # Source of truth: each repo's .ci/manifest.toml. name: Cross-repo trigger (eckit) run-name: Cross-repo trigger (${{ inputs.from-repo }}@${{ inputs.from-sha }}) [${{ inputs.dispatch-id }}] on: workflow_call: inputs: from-repo: description: owner/repo of the dispatcher (upstream producer or downstream consumer) required: true type: string from-sha: description: SHA of the dispatcher commit required: true type: string from-jobs: description: JSON array of package/kinds that triggered us (e.g. '["fortmath/build","fortmath/build-hpc"]') for upstream-change dispatches. '[]' when rebuild-request is true. required: false default: '[]' type: string rebuild-request: description: True when a downstream consumer dispatches us because our artifact was missing. Mutually exclusive with from-jobs. required: false default: false type: boolean branch: description: Dispatcher branch name; we attempt branch-matching against it required: true type: string fallback-ref: description: Ref to check out if branch does not exist in this repo required: false type: string default: main workflow_dispatch: inputs: dispatch-id: description: Correlation id stamped into run-name so the dispatcher can find this run required: false default: '' type: string from-repo: description: owner/repo of the dispatcher (upstream producer or downstream consumer) required: true type: string from-sha: description: SHA of the dispatcher commit required: true type: string from-jobs: description: JSON array of package/kinds that triggered us (e.g. '["fortmath/build","fortmath/build-hpc"]') for upstream-change dispatches. '[]' when rebuild-request is true. required: false default: '[]' type: string rebuild-request: description: True when a downstream consumer dispatches us because our artifact was missing. Mutually exclusive with from-jobs. required: false default: false type: boolean branch: description: Dispatcher branch name; we attempt branch-matching against it required: true type: string fallback-ref: description: Ref to check out if branch does not exist in this repo required: false type: string default: main concurrency: group: cross-repo-trigger-runner-eckit-${{ github.ref }} cancel-in-progress: false env: ARTIFACT_POLL_INTERVAL: ${{ vars.ARTIFACT_POLL_INTERVAL || '60' }} ARTIFACT_S3_ENDPOINT: ${{ secrets.ARTIFACT_S3_ENDPOINT }} ARTIFACT_S3_BUCKET: ${{ secrets.ARTIFACT_S3_BUCKET }} SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} jobs: resolve: if: contains(fromJSON(inputs.from-jobs), 'ecbuild/build') || contains(fromJSON(inputs.from-jobs), 'stack-deps/build') || inputs.rebuild-request runs-on: ubuntu-slim outputs: ref: ${{ steps.pick.outputs.ref }} matrix-build: ${{ steps.r.outputs.matrix-build }} steps: - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - id: pick uses: ecmwf/ci-infrastructure/actions/pick-ref@main with: repo: ecmwf/eckit try-branch: ${{ inputs.branch }} fallback-ref: ${{ inputs.fallback-ref }} token: ${{ steps.mint.outputs.token }} - uses: actions/checkout@v6 with: repository: ecmwf/eckit ref: ${{ steps.pick.outputs.ref }} token: ${{ steps.mint.outputs.token }} - id: r uses: ecmwf/ci-infrastructure/actions/resolve-deps@main with: current-branch: ${{ steps.pick.outputs.ref }} matrix: build token: ${{ steps.mint.outputs.token }} client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} app-private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} eckit__build: needs: - resolve if: contains(fromJSON(inputs.from-jobs), 'ecbuild/build') || contains(fromJSON(inputs.from-jobs), 'stack-deps/build') || inputs.rebuild-request name: eckit/build (${{ matrix._resolved['job-name'] }}) runs-on: ${{ matrix['runs-on'] }} container: image: ${{ matrix.container || '' }} strategy: fail-fast: false matrix: ${{ fromJSON(needs.resolve.outputs.matrix-build) }} steps: - id: mint uses: actions/create-github-app-token@v3 with: client-id: ${{ secrets.CI_PERMISSIONS_APP_CLIENT_ID }} private-key: ${{ secrets.CI_PERMISSIONS_APP_PRIVATE_KEY }} owner: ${{ github.repository_owner }} - id: check_start if: ${{ github.event_name == 'workflow_dispatch' && inputs.from-jobs != '[]' }} uses: ecmwf/ci-infrastructure/actions/report-check-run@main with: token: ${{ steps.mint.outputs.token }} head-repo: ${{ inputs.from-repo }} head-sha: ${{ inputs.from-sha }} name: eckit/build (${{ matrix._resolved['job-name'] }}) details-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} phase: start - name: Announce image uses: ecmwf/ci-infrastructure/actions/announce-image@main - uses: actions/checkout@v6 with: repository: ecmwf/eckit ref: ${{ needs.resolve.outputs.ref }} token: ${{ steps.mint.outputs.token }} - name: Decode matrix-leg id: m shell: bash env: MATRIX_LEG: ${{ toJSON(matrix) }} run: | set -euo pipefail shopt -s inherit_errexit 2>/dev/null || true command -v jq >/dev/null || { echo "::error::jq is required but not installed in this runner/image." >&2 exit 1 } leg="$MATRIX_LEG" require() { local val val=$(jq -r "$1" <<<"$leg") if [ -z "$val" ] || [ "$val" = "null" ]; then echo "::error::matrix-leg field '$2' empty or null (jq -r '$1' returned '$val')." >&2 exit 1 fi printf '%s' "$val" } cxx_compiler=$(require '."cxx-compiler"' cxx-compiler) build_type=$(require '."build-type"' build-type) deps_json=$(require '._resolved.deps | tojson' '_resolved.deps') own_artifact_name=$(require '._resolved."own-artifact-name"' '_resolved.own-artifact-name') { echo "cxx-compiler=${cxx_compiler}" echo "build-type=${build_type}" echo "deps-json=${deps_json}" echo "own-artifact-name=${own_artifact_name}" } >> "$GITHUB_OUTPUT" - name: Fetch resolved deps id: deps uses: ecmwf/ci-infrastructure/actions/fetch-deps@main with: deps-json: ${{ steps.m.outputs.deps-json }} token: ${{ steps.mint.outputs.token }} - name: Build id: build uses: ./.github/actions/build-eckit with: cmake-prefix-path: ${{ steps.deps.outputs.cmake-prefix-path }} cxx-compiler: ${{ steps.m.outputs.cxx-compiler }} build-type: ${{ steps.m.outputs.build-type }} - name: Test run: ctest --test-dir "${{ steps.build.outputs.build-dir }}" --output-on-failure -j "$(nproc)" - name: Publish uses: ecmwf/ci-infrastructure/actions/publish-artifact@main with: install-path: ${{ steps.build.outputs.install-path }} artifact-name: ${{ steps.m.outputs.own-artifact-name }} - name: Report check-run conclusion if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.from-jobs != '[]' }} uses: ecmwf/ci-infrastructure/actions/report-check-run@main with: token: ${{ steps.mint.outputs.token }} head-repo: ${{ inputs.from-repo }} head-sha: ${{ inputs.from-sha }} name: eckit/build (${{ matrix._resolved['job-name'] }}) details-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} phase: finish conclusion: ${{ job.status }} check-run-id: ${{ steps.check_start.outputs.check-run-id }} eckit-2.3.0/.github/workflows/build-wheel-wrapper.yml0000664000175000017500000000157615253173651023017 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Build Python Wrapper Wheel on: # Trigger the workflow manually workflow_dispatch: inputs: use_test_pypi: description: Use test pypi instead of the regular one required: false type: boolean default: false # Allow to be called from another workflow -- eg `cd.yml` workflow_call: inputs: use_test_pypi: description: Use test pypi instead of the regular one required: false type: boolean default: false jobs: python-wrapper-wheel: name: Python Wrapper Wheel uses: ecmwf/reusable-workflows/.github/workflows/python-wrapper-wheel.yml@main with: wheel_directory: python/eckitlib use_test_pypi: ${{ inputs.use_test_pypi }} secrets: inherit eckit-2.3.0/.github/workflows/label-public-pr.yml0000664000175000017500000000054515253173651022105 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # Manage labels of pull requests that originate from forks name: label-public-pr on: pull_request_target: types: [opened, synchronize] jobs: label: uses: ecmwf/reusable-workflows/.github/workflows/label-pr.yml@v2 eckit-2.3.0/.github/workflows/sync.yml0000664000175000017500000000135215253173651020104 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: sync # Controls when the workflow will run on: # Trigger the workflow on all pushes push: branches: - "**" tags: - "**" # Trigger the workflow when a branch or tag is deleted delete: ~ jobs: # Calls a reusable CI workflow to sync the current with a remote repository. # It will correctly handle addition of any new and removal of existing Git objects. sync: name: sync uses: ecmwf/reusable-workflows/.github/workflows/sync.yml@v2 secrets: target_repository: ecsdk/eckit target_username: ClonedDuck target_token: ${{ secrets.BITBUCKET_PAT }} eckit-2.3.0/.github/workflows/cd.yml0000664000175000017500000000056515253173651017523 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: cd on: push: tags: - '**' jobs: deploy: uses: ecmwf/reusable-workflows/.github/workflows/create-package.yml@v2 secrets: inherit wheel: uses: ./.github/workflows/build-wheel-wrapper.yml secrets: inherit eckit-2.3.0/.github/actions/0000775000175000017500000000000015253173651016007 5ustar alastairalastaireckit-2.3.0/.github/actions/build-eckit/0000775000175000017500000000000015253173651020203 5ustar alastairalastaireckit-2.3.0/.github/actions/build-eckit/action.yml0000664000175000017500000000635215253173651022211 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 name: Build eckit from source description: > Configures, builds and installs the already-checked-out eckit against the resolved dependency prefixes, and leaves the build tree in place so the caller can run ctest against it without reconfiguring. Caller must have already run actions/checkout@v6 in the same job — the action reads the source from $GITHUB_WORKSPACE. inputs: cmake-prefix-path: description: 'Semicolon-separated prefixes of the resolved deps (ecbuild, stack-deps)' required: true cxx-compiler: description: 'C++ compiler binary including version (e.g. clang++-18, g++-13)' required: true build-type: description: 'CMake build type (e.g. Release, Debug, RelWithDebInfo)' required: true outputs: install-path: description: 'Absolute path where eckit is installed' value: ${{ steps.set-paths.outputs.install-path }} build-dir: description: 'Absolute path of the build tree, for ctest' value: ${{ steps.set-paths.outputs.build-dir }} runs: using: composite steps: - name: Get install prefix id: prefix uses: ecmwf/ci-infrastructure/actions/install-prefix@main - name: Set paths id: set-paths shell: bash run: | set -euo pipefail { echo "install-path=${{ steps.prefix.outputs.base }}/eckit" echo "build-dir=$RUNNER_TEMP/_eckit-build" } >> "$GITHUB_OUTPUT" - name: Set up sccache if: env.SCCACHE_BUCKET != '' uses: ecmwf/ci-infrastructure/actions/setup-sccache@main with: cache-key-suffix: ${{ inputs.cxx-compiler }}-${{ inputs.build-type }} - name: Select compiler launcher id: launcher shell: bash run: | set -euo pipefail if [ -n "${{ env.SCCACHE_BUCKET }}" ]; then echo "flags=-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache" >> "$GITHUB_OUTPUT" else echo "::notice::SCCACHE_BUCKET unset — compiling without sccache" echo "flags=" >> "$GITHUB_OUTPUT" fi - name: Configure eckit shell: bash env: CMAKE_PREFIX_PATH_IN: ${{ inputs.cmake-prefix-path }} CXX_COMPILER: ${{ inputs.cxx-compiler }} BUILD_TYPE: ${{ inputs.build-type }} INSTALL_PATH: ${{ steps.set-paths.outputs.install-path }} BUILD_DIR: ${{ steps.set-paths.outputs.build-dir }} LAUNCHER: ${{ steps.launcher.outputs.flags }} run: | set -euo pipefail cmake -S "$GITHUB_WORKSPACE" \ -B "$BUILD_DIR" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_CXX_COMPILER="$CXX_COMPILER" \ -DCMAKE_INSTALL_PREFIX="$INSTALL_PATH" \ -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH_IN" \ -DENABLE_TESTS=ON \ -DENABLE_ECKIT_SQL=ON \ -DENABLE_ECKIT_CMD=ON \ $LAUNCHER - name: Build and install eckit shell: bash env: BUILD_DIR: ${{ steps.set-paths.outputs.build-dir }} run: | set -euo pipefail cmake --build "$BUILD_DIR" --parallel "$(nproc)" cmake --install "$BUILD_DIR" eckit-2.3.0/REUSE.toml0000664000175000017500000001316215253173651014572 0ustar alastairalastairversion = 1 SPDX-PackageName = "eckit" SPDX-PackageSupplier = "European Centre for Medium-Range Weather Forecasts (ECMWF) " SPDX-PackageDownloadLocation = "https://github.com/ecmwf/eckit" # This file covers the content that does not carry an SPDX header, either # because the format has no comment syntax, because a header would be read as # data, because the file is meant to be maintained by the user, or because the # content is third-party and must not be re-stamped. An in-file header always # takes precedence over an entry here, so a broad path below does not override # a file that states its own licensing. # ---------------------------------------------------------------------------- # ECMWF-authored content # ---------------------------------------------------------------------------- # The human-facing licence, notice and authorship files, which live outside # LICENSES/. AUTHORS is prose with no comment convention. [[annotations]] path = ["LICENSE", "NOTICE", "AUTHORS"] precedence = "closest" SPDX-FileCopyrightText = "1996- European Centre for Medium-Range Weather Forecasts (ECMWF)" SPDX-License-Identifier = "Apache-2.0" # Files read verbatim. VERSION is read as a bare version string by # python/eckit/setup.py, by pyproject.toml and by ecbuild; # src/eckit/io/rados/README is plain prose. [[annotations]] path = ["VERSION", "src/eckit/io/rados/README"] precedence = "closest" SPDX-FileCopyrightText = "1996- European Centre for Medium-Range Weather Forecasts (ECMWF)" SPDX-License-Identifier = "Apache-2.0" # Runtime configuration, installed to share/eckit/geo and intended to be # edited and maintained by the user. An in-file header would sit above the # user's own edits in the deployed copy. [[annotations]] path = ["share/eckit/geo/**.yaml"] precedence = "closest" SPDX-FileCopyrightText = "1996- European Centre for Medium-Range Weather Forecasts (ECMWF)" SPDX-License-Identifier = "Apache-2.0" # Cython sources: the SPDX copyright line is 90 characters and cython-lint # enforces 88, so these four files declare their licensing here rather than # in a header. A deliberate exception to ADR-010 clause 1. [[annotations]] path = ["python/eckit/src/_eckit/**.pyx", "python/eckit/src/_eckit/**.pxd"] precedence = "closest" SPDX-FileCopyrightText = "1996- European Centre for Medium-Range Weather Forecasts (ECMWF)" SPDX-License-Identifier = "Apache-2.0" # Test fixtures and test data: ECMWF-written YAML parser cases, the eckit::geo # grid caches and archives. The CMakeLists.txt and sources under tests/ carry # their own headers and are unaffected by this entry. [[annotations]] path = ["tests/parser/cfg.1.yaml", "tests/parser/string.yaml", "tests/parser/unicode.yaml", "tests/geo/**.ek", "tests/geo/test.zip"] precedence = "closest" SPDX-FileCopyrightText = "1996- European Centre for Medium-Range Weather Forecasts (ECMWF)" SPDX-License-Identifier = "Apache-2.0" # ---------------------------------------------------------------------------- # Third-party content: original licensing, never re-stamped (ADR-010 clause 7) # ---------------------------------------------------------------------------- # xxHash, vendored unmodified; see src/eckit/contrib/xxhash/eckit-manifest.txt. [[annotations]] path = ["src/eckit/contrib/xxhash/**"] precedence = "closest" SPDX-FileCopyrightText = "2012-2020 Yann Collet" SPDX-License-Identifier = "BSD-2-Clause" # The examples of chapter 2 of the YAML 1.2 specification (2009 revision), # used as parser fixtures. Twenty are verbatim copies. The specification is # distributed under the terms "may be freely copied, provided it is not # modified"; see LICENSES/LicenseRef-YAML-Spec-1.2.txt. [[annotations]] path = ["tests/parser/2.1.yaml", "tests/parser/2.2.yaml", "tests/parser/2.3.yaml", "tests/parser/2.4.yaml", "tests/parser/2.5.yaml", "tests/parser/2.6.yaml", "tests/parser/2.7.yaml", "tests/parser/2.8.yaml", "tests/parser/2.9.yaml", "tests/parser/2.10.yaml", "tests/parser/2.11.yaml", "tests/parser/2.13.yaml", "tests/parser/2.15.yaml", "tests/parser/2.16.yaml", "tests/parser/2.18.yaml", "tests/parser/2.22.yaml", "tests/parser/2.23.yaml", "tests/parser/2.24.yaml", "tests/parser/2.27.yaml", "tests/parser/2.28.yaml"] precedence = "closest" SPDX-FileCopyrightText = "2001-2009 Oren Ben-Kiki, Clark Evans, Ingy dot Net" SPDX-License-Identifier = "LicenseRef-YAML-Spec-1.2" # Eight further examples from the same chapter, modified by ECMWF: comments # reworded (2.12, 2.25, 2.26), a folding indicator and an escape changed # (2.14, 2.17), and three rewritten as broader scalar test cases (2.19, 2.20, # 2.21). Recorded as derived from the specification. [[annotations]] path = ["tests/parser/2.12.yaml", "tests/parser/2.14.yaml", "tests/parser/2.17.yaml", "tests/parser/2.19.yaml", "tests/parser/2.20.yaml", "tests/parser/2.21.yaml", "tests/parser/2.25.yaml", "tests/parser/2.26.yaml"] precedence = "closest" SPDX-FileCopyrightText = ["2001-2009 Oren Ben-Kiki, Clark Evans, Ingy dot Net", "1996- European Centre for Medium-Range Weather Forecasts (ECMWF)"] SPDX-License-Identifier = "LicenseRef-YAML-Spec-1.2" # Eurostat/GISCO "Countries 2024" administrative boundaries at 1:60M, used by # the eckit::geo area tests, and a subset of the same data as GeoJSON. Subject # to the GISCO download provisions: non-commercial use, source acknowledged. [[annotations]] path = ["tests/geo/CNTR_RG_60M_2024_4326.shp.zip", "tests/geo/CNTR_RG_60M_2024_4326_mini.geojson"] precedence = "closest" SPDX-FileCopyrightText = "2024 EuroGeographics for the administrative boundaries; Eurostat/GISCO" SPDX-License-Identifier = "LicenseRef-Eurostat-GISCO" eckit-2.3.0/INSTALL0000664000175000017500000000000015253173651014026 0ustar alastairalastaireckit-2.3.0/regressions/0000775000175000017500000000000015253173651015352 5ustar alastairalastaireckit-2.3.0/regressions/ECKIT-221.cc0000664000175000017500000000232115253173651017020 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 /* FEATURE ECKIT-221 ================= Test broadcasting of file, read on one processor */ #include #include #include #include #include "eckit/io/DataHandle.h" #include "eckit/mpi/Comm.h" #include "eckit/runtime/Tool.h" #include "eckit/utils/Translator.h" using namespace eckit; class MyTool : public Tool { public: MyTool(int argc, char** argv) : Tool(argc, argv) {} virtual ~MyTool() { mpi::finaliseAllComms(); } virtual void run() { PathName filename(argv()[1]); SharedBuffer buffer = mpi::comm().broadcastFile(filename, 0); PathName out = filename + "." + Translator()(mpi::comm().rank()); std::unique_ptr dh(out.fileHandle()); AutoClose closer(*dh); dh->openForWrite(buffer.size()); dh->write(buffer.data(), buffer.size()); } }; int main(int argc, char** argv) { if (argc != 2) { std::cout << "Usage: " << argv[0] << " " << std::endl; return -1; } MyTool tool(argc, argv); return tool.start(); } eckit-2.3.0/regressions/ECKIT-221.sh.in0000775000175000017500000000212615253173651017460 0ustar alastairalastair#!/usr/bin/env bash # SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 set -eux cd @CMAKE_CURRENT_BINARY_DIR@ exe="./ECKIT-221.x" fname="file1.txt" str="abc123" ### Case 1 - Serial function create_file { rm -f $fname* echo "$str" >> $fname echo "$str" >> $fname echo "$str" >> $fname } create_file $exe "$fname" cmp "$fname" "$fname.0" ### Case 2 - Parallel if [ "@eckit_HAVE_MPI@" == "1" ] && [ "@MPIEXEC@" != "MPIEXEC-NOTFOUND" ]; then create_file @MPIEXEC@ @MPI_ARGS@ @MPIEXEC_NUMPROC_FLAG@ 4 $exe "$fname" set +e filed_found=0 for i in $(awk 'BEGIN {for(i=0;i<40;i++) print i}'); do ls -l $fname.0 $fname.1 $fname.2 $fname.3; status=$? if [ "$status" -eq 0 ]; then files_found=1 break else sleep 5 fi echo "Files not yet found or not yet accessible. Retry ($i)" done set -e if [ "${files_found}" -eq 0 ]; then echo "Files not found or not accessible" exit 1 fi for i in 0 1 2 3; do cmp "$fname" "$fname.$i" done fi echo "OK" eckit-2.3.0/regressions/ECKIT-166.cc0000664000175000017500000000125115253173651017031 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 /* BUG ECKIT-166 ============= */ #include #include "eckit/mpi/Comm.h" #include "eckit/runtime/Main.h" using namespace eckit; void run(int argc, char** argv) { Main::initialise(argc, argv); // This should initialise MPI automatically if (mpi::comm().rank() == 0) { std::cout << "mpi::comm().size() = " << mpi::comm().size() << std::endl; } } int main(int argc, char** argv) { std::cout << "begin of main" << std::endl; run(argc, argv); std::cout << "end of main" << std::endl; return 0; } eckit-2.3.0/regressions/CMakeLists.txt0000664000175000017500000000070515253173651020114 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 list( APPEND regressions ECKIT-175 ECKIT-221 ECKIT-166 ) foreach( r ${regressions} ) configure_file( ${r}.sh.in ${r}.sh @ONLY ) ecbuild_add_executable( NOINSTALL TARGET ${r}.x SOURCES ${r}.cc LIBS eckit_mpi eckit ) ecbuild_add_test( TYPE SCRIPT COMMAND ${r}.sh ) endforeach() eckit-2.3.0/regressions/ECKIT-175.cc0000664000175000017500000000410715253173651017034 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 /* BUG ECKIT-175 ============= when Main::initialise(argc,argv) is *not* called or a Tool is *not* used, then everything is OK, and the FileTarget gets destructed as opposed to when Main::initialise(argc,argv) *is* called, or a Tool *is* used. Program expects an integer argument. Possible values: 0 : Test does *not* initialise a Main::instance() Everything works as expected. 1 : Test explicitely starts with Main::initialise(argc,argv) Buffers should be flushed but aren't 2 : Test uses a eckit::Tool to initialise a Main::instance() Buffers should be flushed but aren't Note: The destructor ~FileTarget() is modified to print a message when invoked, so don't merge this */ #include #include #include #include "eckit/log/Log.h" #include "eckit/runtime/Main.h" #include "eckit/runtime/Tool.h" using namespace eckit; void myrun() { Log::setFile("ECKIT-175.out"); Log::info() << "Flushed message in myrun()" << std::endl; Log::info() << "NON Flushed message in myrun()\n"; } class MyTool : public Tool { public: MyTool(int argc, char** argv) : Tool(argc, argv) {} virtual ~MyTool() {} virtual void run() { myrun(); } }; enum { NO_INIT = 1, INIT = 2, TOOL = 3 }; int main(int argc, char** argv) { if (argc != 2) { std::cout << "Usage: " << argv[0] << " " << std::endl; std::cout << " Case values: 1 2 3" << std::endl; return 1; } int test = std::atoi(argv[1]); switch (test) { case 1: { myrun(); break; } case 2: { Main::initialise(argc, argv); myrun(); break; } case 3: { MyTool tool(argc, argv); tool.start(); break; } default: exit(1); break; } return 0; } eckit-2.3.0/regressions/ECKIT-166.sh.in0000775000175000017500000000055215253173651017471 0ustar alastairalastair#!/usr/bin/env bash # SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 set -eux cd @CMAKE_CURRENT_BINARY_DIR@ exe="./ECKIT-166.x" if [ "@eckit_HAVE_MPI@" == "1" ] && [ "@MPIEXEC@" != "MPIEXEC-NOTFOUND" ]; then @MPIEXEC@ @MPI_ARGS@ @MPIEXEC_NUMPROC_FLAG@ 1 $exe fi echo "OK" eckit-2.3.0/regressions/ECKIT-175.sh.in0000775000175000017500000000126515253173651017473 0ustar alastairalastair#!/usr/bin/env bash # SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 set -eux cd @CMAKE_CURRENT_BINARY_DIR@ ### Case 1 rm -f ECKIT-175.out && ./ECKIT-175.x 1 out=$(cat ECKIT-175.out | grep "NON Flushed message in myrun") [[ -z ${out:-} ]] && echo "Failed" && exit 1 ### Case 2 rm -f ECKIT-175.out && ./ECKIT-175.x 2 out=$(cat ECKIT-175.out | grep "NON Flushed message in myrun") [[ -z ${out:-} ]] && echo "Failed" && exit 1 ### Case 3 rm -f ECKIT-175.out && ./ECKIT-175.x 3 out=$(cat ECKIT-175.out | grep "NON Flushed message in myrun") [[ -z ${out:-} ]] && echo "Failed" && exit 1 echo "OK" eckit-2.3.0/tests/0000775000175000017500000000000015253173651014151 5ustar alastairalastaireckit-2.3.0/tests/sql/0000775000175000017500000000000015253173651014750 5ustar alastairalastaireckit-2.3.0/tests/sql/CMakeLists.txt0000664000175000017500000000056115253173651017512 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 set (_sql_tests select simple_functions ) foreach( _tst ${_sql_tests} ) ecbuild_add_test( TARGET eckit_test_sql_${_tst} SOURCES test_${_tst}.cc LIBS eckit_sql ) endforeach() eckit-2.3.0/tests/sql/test_simple_functions.cc0000664000175000017500000006025215253173651021704 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/sql/SQLColumn.h" #include "eckit/sql/SQLDatabase.h" #include "eckit/sql/SQLOutput.h" #include "eckit/sql/SQLParser.h" #include "eckit/sql/SQLSession.h" #include "eckit/sql/SQLStatement.h" #include "eckit/sql/expression/SQLExpressions.h" #include "eckit/sql/expression/function/FunctionFactory.h" #include "eckit/testing/Test.h" using namespace eckit::testing; using namespace eckit::types; using eckit::Log; namespace { //---------------------------------------------------------------------------------------------------------------------- static const std::vector INTEGER_DATA{9999, 8888, 7777, 6666, 5555, 4444, 3333, 2222, 1111}; static const std::vector REAL_DATA{99.9, 88.8, 77.7, 66.6, 55.5, 44.4, 33.3, 22.2, 11.1}; static const std::vector STRING_DATA{ "aaaabbbb", "cccc", "dddd", "eeffg", "hijklmno", "a-string", "a-longer-string", "another-string", ""}; static const std::vector REAL_DATA2{3.0, 4.0, -12.3, 93.7, 11.11, 3.1415, -5.4321, 1.0, -2.0}; static const std::vector DATE_DATA{20210616, 0, 99991231, 100000000, 20210615, 20210614, 20210613, 20210612, 20210611}; static const std::vector TIME_DATA{123456, 123456, 123456, 123456, 0, 654321, 6543, 1000000, 114335}; static const std::vector DATE_DATA2{20210615, 20210614, 20210613, 20210612, 20210611, 20210616, 0, 99991231, 100000000}; static const std::vector TIME_DATA2{0, 654321, 6543, 1000000, 114335, 123456, 123456, 123456, 123456}; class TestTable : public eckit::sql::SQLTable { public: TestTable(eckit::sql::SQLDatabase& db, const std::string& path, const std::string& name) : SQLTable(db, path, name) { addColumn("icol", 0, eckit::sql::type::SQLType::lookup("integer"), false, 0); addColumn("scol", 1, eckit::sql::type::SQLType::lookup("string", 2), false, 0); addColumn("rcol", 2, eckit::sql::type::SQLType::lookup("real"), false, 0); addColumn("rcol2", 3, eckit::sql::type::SQLType::lookup("real"), false, 0); addColumn("dates", 4, eckit::sql::type::SQLType::lookup("integer"), false, 0); addColumn("times", 5, eckit::sql::type::SQLType::lookup("integer"), false, 0); addColumn("dates2", 6, eckit::sql::type::SQLType::lookup("integer"), false, 0); addColumn("times2", 7, eckit::sql::type::SQLType::lookup("integer"), false, 0); } private: class TestTableIterator : public eckit::sql::SQLTableIterator { public: TestTableIterator(const TestTable& owner, const std::vector>& columns) : /* owner_(owner), */ idx_(0), data_(9) { std::vector offsets{0, 1, 3, 4, 5, 6, 7, 8}; std::vector doublesSizes{1, 2, 1, 1, 1, 1, 1, 1}; for (const auto& col : columns) { offsets_.push_back(offsets[col.get().index()]); doublesSizes_.push_back(doublesSizes[col.get().index()]); hasMissing_.push_back(false); missingVals_.push_back(0); } } private: ~TestTableIterator() override {} void rewind() override { idx_ = 0; } bool next() override { if (idx_ < INTEGER_DATA.size()) { copyRow(); idx_++; return true; } return false; } void copyRow() { data_[0] = INTEGER_DATA[idx_]; ::strncpy(reinterpret_cast(&data_[1]), STRING_DATA[idx_].c_str(), 16); data_[3] = REAL_DATA[idx_]; data_[4] = REAL_DATA2[idx_]; data_[5] = DATE_DATA[idx_]; data_[6] = TIME_DATA[idx_]; data_[7] = DATE_DATA2[idx_]; data_[8] = TIME_DATA2[idx_]; } std::vector columnOffsets() const override { return offsets_; } std::vector doublesDataSizes() const override { return doublesSizes_; } std::vector columnsHaveMissing() const override { return hasMissing_; } std::vector missingValues() const override { return missingVals_; } const double* data() const override { return &data_[0]; } // const TestTable& owner_; // unused size_t idx_; std::vector offsets_; std::vector doublesSizes_; std::vector data_; std::vector hasMissing_; std::vector missingVals_; }; eckit::sql::SQLTableIterator* iterator( const std::vector>& columns, std::function metadataUpdateCallback) const override { // TODO: Test callback return new TestTableIterator(*this, columns); } }; //---------------------------------------------------------------------------------------------------------------------- class TestOutput : public eckit::sql::SQLOutput { virtual void prepare(eckit::sql::SQLSelect&) {} virtual void cleanup(eckit::sql::SQLSelect&) {} virtual void reset() {} virtual void flush() {} virtual bool output(const eckit::sql::expression::Expressions& results) { for (const auto& r : results) { r->output(*this); } return true; }; virtual void outputReal(double d, bool) { floatOutput.push_back(d); } virtual void outputDouble(double d, bool) { floatOutput.push_back(d); } virtual void outputInt(double d, bool missing) { if (missing) { intOutput.push_back(-1); } else { intOutput.push_back(d); } } virtual void outputUnsignedInt(double d, bool) { intOutput.push_back(d); } virtual void outputString(const char* s, size_t l, bool) { strOutput.push_back(std::string(s, l)); } virtual void outputBitfield(double d, bool) { intOutput.push_back(d); } virtual unsigned long long count() { return std::max(intOutput.size(), std::max(floatOutput.size(), strOutput.size())); }; public: // visible members std::vector intOutput; std::vector floatOutput; std::vector strOutput; }; //---------------------------------------------------------------------------------------------------------------------- CASE("Test available SQL functions") { // Test that the functions provided as part of eckit are found std::set> functions{{"+", 2}, {"-", 1}, // Unary negative {"-", 2}, {"/", 2}, {"*", 2}, {"<", 2}, {">", 2}, {"<=", 2}, {">=", 2}, {"<>", 2}, {"=", 2}, {"abs", 1}, {"acos", 1}, {"and", 2}, {"asin", 1}, {"atan", 1}, {"atan2", 2}, {"atoi", 1}, {"avg", 1}, {"between", 3}, {"between_exclude_both", 3}, {"between_exclude_second", 3}, {"c2f", 1}, {"c2k", 1}, {"ceil", 1}, {"celsius", 1}, {"circle", 5}, {"cos", 1}, {"cosh", 1}, {"count", 1}, // Aggregate fn {"day", 1}, {"dd", 2}, {"deg2rad", 1}, {"degrees", 1}, {"dir", 2}, {"direction", 2}, {"dist", 5}, {"distance", 4}, {"dotp", 2}, {"exp", 1}, {"f2c", 1}, {"f2k", 1}, {"fabs", 1}, {"fahrenheit", 1}, {"ff", 2}, {"first", 1}, {"floor", 1}, {"fmod", 2}, {"hour", 1}, {"ibits", 3}, {"in", -1}, {"int", 1}, {"isnull", 1}, {"jd", 2}, {"join", 2}, {"julian", 2}, {"julian_date", 2}, {"julian_seconds", 2}, {"k2c", 1}, {"k2f", 1}, {"kelvin", 1}, {"km", 1}, {"km", 4}, {"last", 1}, {"ldexp_double", 2}, {"lg", 1}, {"like", 2}, {"ln", 1}, {"log", 1}, {"log10", 1}, {"max", 1}, {"mean", 1}, {"min", 1}, {"minute", 1}, {"minutes", 1}, {"mod", 2}, {"month", 1}, {"nint", 1}, {"norm", 2}, {"not", 1}, {"not_between", 3}, {"not_in", -1}, {"not_null", 1}, {"null", 1}, {"nvl", 2}, {"or", 2}, {"pow", 2}, {"rad", 5}, {"rad2deg", 1}, {"radians", 1}, {"rlike", 2}, {"rms", 1}, {"rownumber", 0}, {"second", 1}, {"seconds", 1}, {"sin", 1}, {"sinh", 1}, {"speed", 2}, {"sqrt", 1}, {"stddev", 1}, {"stdev", 1}, {"stdevp", 1}, {"sum", 1}, {"tan", 1}, {"tanh", 1}, {"tdiff", 4}, {"thin", 2}, {"timestamp", 2}, {"trunc", 1}, {"twice", 1}, {"var", 1}, {"varp", 1}, {"year", 1}}; auto info = eckit::sql::expression::function::FunctionFactory::instance().functionsInfo(); for (const auto& i : info) { auto it = functions.find(std::make_pair(i.name, i.arity)); if (it != functions.end()) { functions.erase(it); } else { Log::info() << "Not matching: " << i.name << ": " << i.arity << std::endl; } } if (!functions.empty()) { Log::error() << "Not all functions found: " << std::endl; for (const auto& f : functions) { Log::error() << " name: " << f.first << ", arity: " << f.second << std::endl; } } EXPECT(functions.empty()); } CASE("Functions using test data") { // Construct session with TestOutput to get the output eckit::sql::SQLSession session(std::unique_ptr(new TestOutput)); eckit::sql::SQLDatabase& db(session.currentDatabase()); db.addTable(new TestTable(db, "a/b/c.path", "table1")); SECTION("Test SQL comparisons") { std::string sql = "select icol > 3333, icol >= 3333 from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.strOutput.size() == 0); EXPECT(o.floatOutput.size() == 18); for (size_t i = 0; i < 12; i++) { EXPECT(o.floatOutput[i]); } EXPECT(!o.floatOutput[12]); EXPECT(o.floatOutput[13]); for (size_t i = 14; i < 18; i++) { EXPECT(!o.floatOutput[i]); } } SECTION("Test simple arithmetic -- addition/subtraction") { std::string sql = "select icol + 15.3, rcol + 13.5, icol + rcol, icol - 15.3, rcol - 13.5, icol - rcol from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 54); EXPECT(o.strOutput.size() == 0); // i = [9999, 8888, 7777, 6666, 5555, 4444, 3333, 2222, 1111] // r = [99.9, 88.8, 77.7, 66.6, 55.5, 44.4, 33.3, 22.2, 11.1] // for ii, rr in zip(i, r): // print ", ".join("{:7.1f}".format(v) for v in (ii+15.3, rr+13.5, ii+rr, ii-15.3, rr-13.5, ii-rr)) std::vector expected{ 10014.3, 113.4, 10098.9, 9983.7, 86.4, 9899.1, 8903.3, 102.3, 8976.8, 8872.7, 75.3, 8799.2, 7792.3, 91.2, 7854.7, 7761.7, 64.2, 7699.3, 6681.3, 80.1, 6732.6, 6650.7, 53.1, 6599.4, 5570.3, 69.0, 5610.5, 5539.7, 42.0, 5499.5, 4459.3, 57.9, 4488.4, 4428.7, 30.9, 4399.6, 3348.3, 46.8, 3366.3, 3317.7, 19.8, 3299.7, 2237.3, 35.7, 2244.2, 2206.7, 8.7, 2199.8, 1126.3, 24.6, 1122.1, 1095.7, -2.4, 1099.9, }; EXPECT(is_approximately_equal(o.floatOutput, expected, 0.000001)); } SECTION("Test simple arithmetic -- multiplication/division") { std::string sql = "select icol * 15.3, rcol * 13.5, icol * rcol, icol / 15.3, rcol / 13.5, icol / rcol from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 54); EXPECT(o.strOutput.size() == 0); // i = [9999, 8888, 7777, 6666, 5555, 4444, 3333, 2222, 1111] // r = [99.9, 88.8, 77.7, 66.6, 55.5, 44.4, 33.3, 22.2, 11.1] // for ii, rr in zip(i, r): // print ", ".join("{:7.4f}".format(v) for v in (ii*15.3, rr*13.5, ii*rr, ii/15.3, rr/13.5, ii/rr)) std::vector expected{ 152984.7000, 1348.6500, 998900.1000, 653.5294, 7.4000, 100.0901, 135986.4000, 1198.8000, 789254.4000, 580.9150, 6.5778, 100.0901, 118988.1000, 1048.9500, 604272.9000, 508.3007, 5.7556, 100.0901, 101989.8000, 899.1000, 443955.6000, 435.6863, 4.9333, 100.0901, 84991.5000, 749.2500, 308302.5000, 363.0719, 4.1111, 100.0901, 67993.2000, 599.4000, 197313.6000, 290.4575, 3.2889, 100.0901, 50994.9000, 449.5500, 110988.9000, 217.8431, 2.4667, 100.0901, 33996.6000, 299.7000, 49328.4000, 145.2288, 1.6444, 100.0901, 16998.3000, 149.8500, 12332.1000, 72.6144, 0.8222, 100.0901}; EXPECT(is_approximately_equal(o.floatOutput, expected, 0.0001)); } SECTION("Test SQL conditional composition AND") { std::string sql = "select icol > 3333 and rcol <= 66.6 from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 9); EXPECT(o.strOutput.size() == 0); for (size_t i = 0; i < 3; i++) { EXPECT(!o.floatOutput[i]); } for (size_t i = 3; i < 5; i++) { EXPECT(o.floatOutput[i]); } for (size_t i = 6; i < 9; i++) { EXPECT(!o.floatOutput[i]); } } SECTION("Test SQL conditional composition OR") { std::string sql = "select icol <= 3333 or rcol > 66.6 from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 9); EXPECT(o.strOutput.size() == 0); for (size_t i = 0; i < 3; i++) { EXPECT(o.floatOutput[i]); } for (size_t i = 3; i < 5; i++) { EXPECT(!o.floatOutput[i]); } for (size_t i = 6; i < 9; i++) { EXPECT(o.floatOutput[i]); } } SECTION("Test SQL conditional composition NOT OR") { std::string sql = "select not (icol <= 3333 or rcol > 66.6) from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 9); EXPECT(o.strOutput.size() == 0); for (size_t i = 0; i < 3; i++) { EXPECT(!o.floatOutput[i]); } for (size_t i = 3; i < 5; i++) { EXPECT(o.floatOutput[i]); } for (size_t i = 6; i < 9; i++) { EXPECT(!o.floatOutput[i]); } } SECTION("Test SQL aggregates") { std::string sql = "select count(*), count(icol), mean(icol), sum(icol) from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 4); EXPECT(o.strOutput.size() == 0); std::vector expected{9, 9, 5555, 49995}; EXPECT(o.floatOutput == expected); } SECTION("Test SQL rownumber() with other column") { std::string sql = "select icol,rownumber() from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 18); EXPECT(o.floatOutput.size() == 0); EXPECT(o.strOutput.size() == 0); ASSERT(INTEGER_DATA.size() == 9); std::vector expectedInt; for (int i = 0; i < 9; ++i) { expectedInt.push_back(INTEGER_DATA[i]); expectedInt.push_back(i + 1); } EXPECT(o.intOutput == expectedInt); } SECTION("Test SQL rownumber() alone") { std::string sql = "select rownumber() from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); Log::info() << "intOutput size: " << o.intOutput.size() << std::endl; EXPECT(o.intOutput.size() == 9); EXPECT(o.floatOutput.size() == 0); EXPECT(o.strOutput.size() == 0); std::vector expectedInt{1, 2, 3, 4, 5, 6, 7, 8, 9}; EXPECT(o.intOutput == expectedInt); } SECTION("Test dotp()") { std::string sql = "select dotp(rcol, rcol2) from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 0); EXPECT(o.floatOutput.size() == 1); EXPECT(o.strOutput.size() == 0); EXPECT(is_approximately_equal(o.floatOutput[0], 6514.80867, 1e-7)); } SECTION("Test TIMESTAMP()") { std::string sql = "select timestamp(dates, times) from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); Log::info() << "intOutput(): " << o.intOutput << std::endl; EXPECT(o.intOutput.size() == 9); EXPECT(o.floatOutput.size() == 0); EXPECT(o.strOutput.size() == 0); std::vector expectedInt{20210616123456, 123456, 99991231123456, -1, 20210615000000, -1, 20210613006543, -1, 20210611114335}; EXPECT(o.intOutput == expectedInt); } SECTION("Test TDIFF()") { std::string sql = "select tdiff(dates, times, dates2, times2) from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); TestOutput& o(static_cast(session.output())); EXPECT(o.intOutput.size() == 9); EXPECT(o.floatOutput.size() == 0); EXPECT(o.strOutput.size() == 0); std::vector expectedInt{131696, -1, -1, -1, 303385, -1, -1, -1, -1}; EXPECT(o.intOutput == expectedInt); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/sql/test_select.cc0000664000175000017500000004742415253173651017610 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/sql/SQLColumn.h" #include "eckit/sql/SQLDatabase.h" #include "eckit/sql/SQLOutput.h" #include "eckit/sql/SQLParser.h" #include "eckit/sql/SQLSelect.h" #include "eckit/sql/SQLSession.h" #include "eckit/sql/SQLStatement.h" #include "eckit/sql/expression/SQLExpressions.h" #include "eckit/sql/type/SQLBitfield.h" #include "eckit/testing/Test.h" using namespace eckit::testing; namespace { //---------------------------------------------------------------------------------------------------------------------- static const std::vector INTEGER_DATA{9999, 8888, 7777, 6666, 6666, 6666, 4444, 3333, 2222, 1111, 1234}; static const std::vector REAL_DATA{99.9, 88.8, 77.7, 66.6, 66.6, 88.8, 44.4, 33.3, 22.2, 11.1, 12.3}; static const std::vector STRING_DATA{ "cccc", "a-longer-string", "cccc", "cccc", "", "hijklmno", "aaaabbbb", "a-longer-string", "another-string", "another-string2", ""}; static const std::vector BITFIELD_DATA{0, 1, 2, 4, 8, 9, 10, 12, 13, 15, 11}; static const std::vector BF1_DATA{0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1}; static const std::vector BF2_DATA{0, 0, 1, 2, 0, 0, 1, 2, 2, 3, 1}; static const std::vector BF3_DATA{0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1}; class TestTable : public eckit::sql::SQLTable { public: TestTable(eckit::sql::SQLDatabase& db, const std::string& path, const std::string& name) : SQLTable(db, path, name) { addColumn("icol", 0, eckit::sql::type::SQLType::lookup("integer"), false, 0); addColumn("scol", 1, eckit::sql::type::SQLType::lookup("string", 1), false, 0); addColumn("rcol", 2, eckit::sql::type::SQLType::lookup("real"), false, 0); std::vector bfNames = {"bf1", "bf2", "bf3"}; std::vector bfSizes = {1, 2, 1}; std::string bfType = eckit::sql::type::SQLBitfield::make("Bitfield", bfNames, bfSizes, "dummy"); addColumn("bfcolumn", 3, eckit::sql::type::SQLType::lookup(bfType), false, 0, true, std::make_pair(bfNames, bfSizes)); addColumn("bgcolumn@tbl1", 4, eckit::sql::type::SQLType::lookup(bfType), false, 0, true, std::make_pair(bfNames, bfSizes)); addColumn("bgcolumn@tbl2", 5, eckit::sql::type::SQLType::lookup(bfType), false, 0, true, std::make_pair(bfNames, bfSizes)); addColumn("preselected.bfcolumn", 6, eckit::sql::type::SQLType::lookup("integer"), false, 0); } private: class TestTableIterator : public eckit::sql::SQLTableIterator { public: TestTableIterator(const TestTable& owner, const std::vector>& columns, std::function updateCallback) : /* owner_(owner), */ idx_(0), data_(8), updateCallback_(updateCallback) { std::vector offsets{0, 1, 2, 3, 4, 5, 6}; std::vector doublesSizes{1, 1, 1, 1, 1, 1, 1}; for (const auto& col : columns) { columnIndexes_.push_back(col.get().index()); offsets_.push_back(offsets[col.get().index()]); doublesSizes_.push_back(doublesSizes[col.get().index()]); hasMissing_.push_back(false); missingVals_.push_back(0); } } private: ~TestTableIterator() override {} void rewind() override { idx_ = 0; } bool next() override { // After the first element, we resize things, so we can test the callback and type resizing // functionality if (idx_ == 0) { offsets_.clear(); doublesSizes_.clear(); std::vector offsets{0, 1, 3, 4, 5, 6, 7}; std::vector doublesSizes{1, 2, 1, 1, 1, 1, 1}; for (const auto& idx : columnIndexes_) { offsets_.push_back(offsets[idx]); doublesSizes_.push_back(doublesSizes[idx]); } updateCallback_(*this); } if (idx_ < INTEGER_DATA.size()) { copyRow(); idx_++; return true; } return false; } void copyRow() { data_[0] = INTEGER_DATA[idx_]; ::strncpy(reinterpret_cast(&data_[1]), STRING_DATA[idx_].c_str(), 16); data_[3] = REAL_DATA[idx_]; data_[4] = BITFIELD_DATA[idx_]; data_[5] = BITFIELD_DATA[idx_]; data_[6] = BITFIELD_DATA[idx_]; data_[7] = INTEGER_DATA[idx_]; } std::vector columnOffsets() const override { return offsets_; } std::vector doublesDataSizes() const override { return doublesSizes_; } std::vector columnsHaveMissing() const override { return hasMissing_; } std::vector missingValues() const override { return missingVals_; } const double* data() const override { return &data_[0]; } // const TestTable& owner_; // unused size_t idx_; std::vector offsets_; std::vector doublesSizes_; std::vector columnIndexes_; std::vector hasMissing_; std::vector missingVals_; std::vector data_; std::function updateCallback_; }; eckit::sql::SQLTableIterator* iterator( const std::vector>& columns, std::function metadataUpdateCallback) const override { return new TestTableIterator(*this, columns, metadataUpdateCallback); } }; //---------------------------------------------------------------------------------------------------------------------- class TestOutput : public eckit::sql::SQLOutput { void cleanup(eckit::sql::SQLSelect&) override {} void reset() override { intOutput_.clear(); floatOutput_.clear(); strOutput_.clear(); } void flush() override { std::swap(intOutput_, intOutput); std::swap(floatOutput_, floatOutput); std::swap(strOutput_, strOutput); } bool output(const eckit::sql::expression::Expressions& results) override { for (const auto& r : results) { r->output(*this); } return true; } void prepare(eckit::sql::SQLSelect& sql) override { columnNames.clear(); const eckit::sql::expression::Expressions& columns(sql.output()); for (const auto& col : columns) { columnNames.push_back(col->title()); } } void outputReal(double d, bool) override { floatOutput_.push_back(d); } void outputDouble(double d, bool) override { floatOutput_.push_back(d); } void outputInt(double d, bool) override { intOutput_.push_back(d); } void outputUnsignedInt(double d, bool) override { intOutput_.push_back(d); } void outputString(const char* s, size_t l, bool missing) override { if (missing) { strOutput_.push_back(std::string()); } else { ASSERT(s); strOutput_.push_back(std::string(s, l)); } } void outputBitfield(double d, bool) override { intOutput_.push_back(d); } unsigned long long count() override { return std::max(intOutput.size(), std::max(floatOutput_.size(), strOutput_.size())); } public: // visible members std::vector intOutput_; std::vector floatOutput_; std::vector strOutput_; std::vector intOutput; std::vector floatOutput; std::vector strOutput; std::vector columnNames; }; //---------------------------------------------------------------------------------------------------------------------- // TODO: Test explicit vs implicit table // TODO: Test if database has a table CASE("Test SQL database setup") { eckit::sql::SQLDatabase db; db.addTable(new TestTable(db, "a/b/c.path", "table1")); db.addTable(new TestTable(db, "d/e/f.path", "table2")); } CASE("Select from constructed table") { eckit::sql::SQLSession session(std::unique_ptr(new TestOutput)); eckit::sql::SQLDatabase& db(session.currentDatabase()); db.addTable(new TestTable(db, "a/b/c.path", "table1")); TestOutput& o(static_cast(session.output())); // All of the different types of select from this standard table. SECTION("Test SQL select columns") { std::string sql = "select scol,icol from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.intOutput == INTEGER_DATA); EXPECT(o.floatOutput.empty()); EXPECT(o.strOutput == STRING_DATA); EXPECT(o.columnNames == std::vector({"scol", "icol"})); } SECTION("Test SQL select all") { std::string sql = "select * from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); for (int row = 0; row < INTEGER_DATA.size(); ++row) { EXPECT(o.intOutput[5 * row] == INTEGER_DATA[row]); for (int j = 0; j < 3; ++j) { EXPECT(o.intOutput[5 * row + 1 + j] == BITFIELD_DATA[row]); } EXPECT(o.intOutput[5 * row + 4] == INTEGER_DATA[row]); } EXPECT(o.floatOutput == REAL_DATA); EXPECT(o.strOutput == STRING_DATA); EXPECT(o.columnNames == std::vector({"icol", "scol", "rcol", "bfcolumn", "bgcolumn@tbl1", "bgcolumn@tbl2", "preselected.bfcolumn"})); } SECTION("Test SQL select where") { std::string sql = "select rcol from table1 where icol > 4000"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.intOutput.empty()); std::vector expected{99.9, 88.8, 77.7, 66.6, 66.6, 88.8, 44.4}; EXPECT(o.floatOutput == expected); EXPECT(o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"rcol"})); } SECTION("Test SQL select where long string") { std::string sql = "select rcol from table1 where scol == \"another-string\""; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.intOutput.empty()); std::vector expected{22.2}; EXPECT(o.floatOutput == expected); EXPECT(o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"rcol"})); } SECTION("Test SQL select where string is not empty") { for (const auto& sql : {"select rcol from table1 where scol != \"\"", "select rcol from table1 where scol != ''"}) { eckit::sql::SQLParser::parseString(session, sql); session.statement().execute(); EXPECT(o.intOutput.empty()); std::vector expected{99.9, 88.8, 77.7, 66.6, 88.8, 44.4, 33.3, 22.2, 11.1}; EXPECT(o.floatOutput == expected); EXPECT(o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"rcol"})); } } SECTION("Test SQL select distinct") { std::vector queries = {"select distinct icol from table1", "select distinct rcol from table1", "select distinct scol from table1", "select distinct icol,rcol from table1"}; for (size_t i = 0; i < queries.size(); i++) { eckit::sql::SQLParser().parseString(session, queries[i]); session.statement().execute(); if (i == 0) { EXPECT(o.intOutput == std::vector({9999, 8888, 7777, 6666, 4444, 3333, 2222, 1111, 1234})); EXPECT(o.floatOutput.empty() && o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"icol"})); } else if (i == 1) { EXPECT(o.floatOutput == std::vector({99.9, 88.8, 77.7, 66.6, 44.4, 33.3, 22.2, 11.1, 12.3})); EXPECT(o.intOutput.empty() && o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"rcol"})); } else if (i == 2) { EXPECT(o.strOutput == std::vector({"cccc", "a-longer-string", "", "hijklmno", "aaaabbbb", "another-string", "another-string2"})); EXPECT(o.intOutput.empty() && o.floatOutput.empty()); EXPECT(o.columnNames == std::vector({"scol"})); } else { // Test that it is finding unique rows, not values (n.b. 6666/88.8) EXPECT(o.intOutput == std::vector({9999, 8888, 7777, 6666, 6666, 4444, 3333, 2222, 1111, 1234})); EXPECT(o.floatOutput == std::vector({99.9, 88.8, 77.7, 66.6, 88.8, 44.4, 33.3, 22.2, 11.1, 12.3})); EXPECT(o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"icol", "rcol"})); } } } SECTION("Test SQL select order_by") { std::vector queries = { "select icol from table1 order by icol ASC", "select icol from table1 order by icol", "select icol from table1 order by icol DESC", "select distinct icol from table1 order by icol", "select distinct rcol,scol from table1 order by scol DESC, icol ASC", // n.b. different order and result // contents }; std::vector> vals = {{1111, 1234, 2222, 3333, 4444, 6666, 6666, 6666, 7777, 8888, 9999}, {1111, 1234, 2222, 3333, 4444, 6666, 6666, 6666, 7777, 8888, 9999}, {9999, 8888, 7777, 6666, 6666, 6666, 4444, 3333, 2222, 1234, 1111}, {1111, 1234, 2222, 3333, 4444, 6666, 7777, 8888, 9999}}; for (size_t i = 0; i < queries.size(); i++) { eckit::sql::SQLParser().parseString(session, queries[i]); session.statement().execute(); if (i < 4) { EXPECT(o.intOutput == vals[i]); EXPECT(o.floatOutput.empty() && o.strOutput.empty()); EXPECT(o.columnNames == std::vector({"icol"})); } else { EXPECT(o.intOutput.empty()); EXPECT(o.floatOutput == std::vector({88.8, 66.6, 77.7, 99.9, 11.1, 22.2, 44.4, 33.3, 88.8, 12.3, 66.6})); EXPECT(o.strOutput == std::vector({"hijklmno", "cccc", "cccc", "cccc", "another-string2", "another-string", "aaaabbbb", "a-longer-string", "a-longer-string", "", ""})); EXPECT(o.columnNames == std::vector({"rcol", "scol"})); } } } SECTION("Test selection of bitfield bit columns") { // n.b. ensure that we check the ability to: // i) Find fields in a bitfield column // ii) Find fields in a bitfield column (plus table name) // iii) Find fields in a bitfield column (auto table name) std::string sql = "select bfcolumn.bf1, bgcolumn.bf3@tbl1, bfcolumn.bf2, bgcolumn.bf1@tbl2 from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.columnNames == std::vector({"bfcolumn.bf1", "bgcolumn.bf3@tbl1", "bfcolumn.bf2", "bgcolumn.bf1@tbl2"})); for (int row = 0; row < INTEGER_DATA.size(); ++row) { EXPECT(o.intOutput[4 * row] == BF1_DATA[row]); EXPECT(o.intOutput[4 * row + 1] == BF3_DATA[row]); EXPECT(o.intOutput[4 * row + 2] == BF2_DATA[row]); EXPECT(o.intOutput[4 * row + 3] == BF1_DATA[row]); } } SECTION("Test selection of a column that looks-like a bitfield") { std::string sql = "select preselected.bfcolumn from table1"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.columnNames == std::vector({"preselected.bfcolumn"})); for (int row = 0; row < INTEGER_DATA.size(); ++row) { EXPECT(o.intOutput[row] == INTEGER_DATA[row]); } } } // Testing SQL select from standard table CASE("Test with implicit tables") { eckit::sql::SQLSession session(std::unique_ptr(new TestOutput)); eckit::sql::SQLDatabase& db(session.currentDatabase()); db.addImplicitTable(new TestTable(db, "a/b/c.path", "table1")); TestOutput& o(static_cast(session.output())); // All of the different types of select from this standard table. SECTION("Test SQL select columns") { std::string sql = "select scol,icol"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.intOutput == INTEGER_DATA); EXPECT(o.floatOutput.empty()); EXPECT(o.strOutput == STRING_DATA); EXPECT(o.columnNames == std::vector({"scol", "icol"})); } SECTION("Test SQL select all") { std::string sql = "select *"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); for (int row = 0; row < INTEGER_DATA.size(); ++row) { EXPECT(o.intOutput[5 * row] == INTEGER_DATA[row]); for (int j = 0; j < 3; ++j) { EXPECT(o.intOutput[5 * row + 1 + j] == BITFIELD_DATA[row]); } EXPECT(o.intOutput[5 * row + 4] == INTEGER_DATA[row]); } EXPECT(o.floatOutput == REAL_DATA); EXPECT(o.strOutput == STRING_DATA); EXPECT(o.columnNames == std::vector({"icol", "scol", "rcol", "bfcolumn", "bgcolumn@tbl1", "bgcolumn@tbl2", "preselected.bfcolumn"})); } SECTION("Test selection of bitfield bit columns") { std::string sql = "select bfcolumn.bf1, bgcolumn.bf3@tbl1, bfcolumn.bf2, bgcolumn.bf1@tbl2"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.columnNames == std::vector({"bfcolumn.bf1", "bgcolumn.bf3@tbl1", "bfcolumn.bf2", "bgcolumn.bf1@tbl2"})); for (int row = 0; row < INTEGER_DATA.size(); ++row) { EXPECT(o.intOutput[4 * row] == BF1_DATA[row]); EXPECT(o.intOutput[4 * row + 1] == BF3_DATA[row]); EXPECT(o.intOutput[4 * row + 2] == BF2_DATA[row]); EXPECT(o.intOutput[4 * row + 3] == BF1_DATA[row]); } } SECTION("Test selection of a column that looks-like a bitfield") { std::string sql = "select preselected.bfcolumn"; eckit::sql::SQLParser().parseString(session, sql); session.statement().execute(); EXPECT(o.columnNames == std::vector({"preselected.bfcolumn"})); for (int row = 0; row < INTEGER_DATA.size(); ++row) { EXPECT(o.intOutput[row] == INTEGER_DATA[row]); } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/0000775000175000017500000000000015253173651015311 5ustar alastairalastaireckit-2.3.0/tests/utils/test_string_tools.cc0000664000175000017500000001040215253173651021402 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/log/Log.h" #include "eckit/runtime/Tool.h" #include "eckit/types/Types.h" #include "eckit/utils/StringTools.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Convert to lower") { std::string istr = "Foo Bar"; std::string ostr = StringTools::lower(istr); EXPECT(ostr == "foo bar"); } CASE("Convert to upper") { std::string istr = "Foo Bar"; std::string ostr = StringTools::upper(istr); EXPECT(ostr == "FOO BAR"); } CASE("substitute") { StringDict m; m["class"] = "od"; m["stream"] = "oper"; string in("{class}:none:{stream}"); string out = StringTools::substitute(in, m); EXPECT(out == "od:none:oper"); } CASE("listVariables") { string in("{class}:none:{stream}"); StringList out = StringTools::listVariables(in); EXPECT(out.size() == 2); EXPECT(out[0] == "class"); EXPECT(out[1] == "stream"); } CASE("startsWith") { string in("_lolo_test"); string s1("_lolo"); string s2("lolo"); string s3("_lolo_test_bigger"); EXPECT(!StringTools::startsWith(in, "")); EXPECT(StringTools::startsWith(in, in)); EXPECT(StringTools::startsWith(in, s1)); EXPECT(StringTools::startsWith(in, "_")); EXPECT(!StringTools::startsWith(in, s2)); EXPECT(!StringTools::startsWith(in, s3)); } CASE("endsWith") { string in("_lolo_test"); EXPECT(!StringTools::endsWith(in, "")); EXPECT(StringTools::endsWith(in, in)); EXPECT(StringTools::endsWith(in, "t")); string s1("_test"); EXPECT(StringTools::endsWith(in, s1)); string s2("lolo"); EXPECT(!StringTools::endsWith(in, s2)); string s3("_lolo_test333"); EXPECT(!StringTools::endsWith(in, s3)); } CASE("trim") { string t1(" lolo_test "); EXPECT(StringTools::trim(t1) == string("lolo_test")); string t2(" lolo_test"); EXPECT(StringTools::trim(t2) == string("lolo_test")); string t3("lolo_test "); EXPECT(StringTools::trim(t3) == string("lolo_test")); string t4(""); EXPECT(StringTools::trim(t4) == string("")); string t5("nothing_here"); EXPECT(StringTools::trim(t5) == string("nothing_here")); string t6("XXXXXXusefullXXXXX"); EXPECT(StringTools::trim(t6, "X") == string("usefull")); string t7("0000010"); EXPECT(StringTools::trim(t7, "0") == string("1")); } CASE("front_trim") { string t1(" lolo_test "); EXPECT(StringTools::front_trim(t1) == string("lolo_test ")); string t2(" lolo_test"); EXPECT(StringTools::front_trim(t2) == string("lolo_test")); string t3("lolo_test "); EXPECT(StringTools::front_trim(t3) == string("lolo_test ")); string t4(""); EXPECT(StringTools::front_trim(t4) == string("")); string t5("nothing_here"); EXPECT(StringTools::front_trim(t5) == string("nothing_here")); string t6("XXXXXXusefullXXXXX"); EXPECT(StringTools::front_trim(t6, "X") == string("usefullXXXXX")); string t7("0000010"); EXPECT(StringTools::front_trim(t7, "0") == string("10")); } CASE("back_trim") { string t1(" lolo_test "); Log::info() << StringTools::back_trim(t1) << std::endl; EXPECT(StringTools::back_trim(t1) == string(" lolo_test")); string t2(" lolo_test"); EXPECT(StringTools::back_trim(t2) == string(" lolo_test")); string t3("lolo_test "); EXPECT(StringTools::back_trim(t3) == string("lolo_test")); string t4(""); EXPECT(StringTools::back_trim(t4) == string("")); string t5("nothing_here"); EXPECT(StringTools::back_trim(t5) == string("nothing_here")); string t6("XXXXXXusefullXXXXX"); EXPECT(StringTools::back_trim(t6, "X") == string("XXXXXXusefull")); string t7("0000010"); EXPECT(StringTools::back_trim(t7, "0") == string("000001")); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_tokenizer.cc0000664000175000017500000000660715253173651020702 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/log/Log.h" #include "eckit/runtime/Tool.h" #include "eckit/types/Types.h" #include "eckit/utils/Tokenizer.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- template void test_single() { std::string source(":lolo1:lolo2::lolo3"); Container target; Tokenizer parse(":"); parse(source, target); EXPECT(target.size() == 3); size_t c = 1; for (typename Container::const_iterator i = target.begin(); i != target.end(); ++i, ++c) { ostringstream s; s << "lolo" << c; // std::cout << *i << " ??? " << s.str() << std::endl; EXPECT(*i == s.str()); } } //---------------------------------------------------------------------------------------------------------------------- template void test_multi() { Container target; Tokenizer parse("-:;"); std::string source1("-lolo0-lolo1-lolo2;lolo3:-lolo4-"); parse(source1, target); EXPECT(target.size() == 5); size_t c = 0; for (typename Container::const_iterator i = target.begin(); i != target.end(); ++i, ++c) { ostringstream s; s << "lolo" << c; // std::cout << *i << " ??? " << s.str() << std::endl; EXPECT(*i == s.str()); } std::string source2("-lolo5-lolo6-lolo7;lolo8:lolo9-"); parse(source2, target); EXPECT(target.size() == 10); c = 0; for (typename Container::const_iterator i = target.begin(); i != target.end(); ++i, ++c) { ostringstream s; s << "lolo" << c; // std::cout << *i << " ??? " << s.str() << std::endl; EXPECT(*i == s.str()); } } //---------------------------------------------------------------------------------------------------------------------- void test_keep_empty_list() { StringList target; Tokenizer parse(":", true); std::string source1(":4i:2100:::01:::::."); parse(source1, target); Log::info() << target << std::endl; EXPECT(target.size() == 11); } //---------------------------------------------------------------------------------------------------------------------- void test_keep_empty_set() { StringSet target; Tokenizer parse(":", true); std::string source1(":4i:2100:::01:::::."); parse(source1, target); Log::info() << target << std::endl; EXPECT(target.size() == 5); // 5 uniqe elments } //---------------------------------------------------------------------------------------------------------------------- CASE("Test Tokenizer StringList") { test_single(); test_multi(); test_keep_empty_list(); } CASE("Test Tokenizer StringSet") { test_single(); test_multi(); test_keep_empty_set(); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/CMakeLists.txt0000664000175000017500000000526315253173651020057 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ### test data files list( APPEND test_data_files 2t_sfc.grib 2t_sfc_regrid.grib vo-d_6ml.grib u-v_6ml.grib q_6ml_regrid.grib ) if(HAVE_EXTRA_TESTS) ecbuild_get_test_multidata( TARGET get_eckit_test_data NAMES ${test_data_files} NOCHECK ) endif() ecbuild_add_test( TARGET eckit_test_utils_byteswap SOURCES test_byteswap.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_string_tools SOURCES test_string_tools.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_translator SOURCES test_translator.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_tokenizer SOURCES test_tokenizer.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_semanticversion SOURCES test_semanticversion.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_rendezvoushash SOURCES test_rendezvoushash.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_rle SOURCES test_rle.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_compressor SOURCES test_compressor.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_hashing CONDITION eckit_HAVE_SSL INCLUDES "${OPENSSL_INCLUDE_DIR}" SOURCES test_hashing.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_hash_performance CONDITION HAVE_EXTRA_TESTS SOURCES hash-performance.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_utils_compression_performance CONDITION HAVE_EXTRA_TESTS TEST_DEPENDS get_eckit_test_data SOURCES compression-performance.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_regex SOURCES test_regex.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_literals SOURCES test_literals.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_safe_casts SOURCES test_safe_casts.cc LIBS eckit ) eckit-2.3.0/tests/utils/compression-performance.cc0000664000175000017500000001050215253173651022456 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include "eckit/filesystem/PathName.h" #include "eckit/io/Buffer.h" #include "eckit/io/DataHandle.h" #include "eckit/log/Bytes.h" #include "eckit/log/Seconds.h" #include "eckit/log/Timer.h" #include "eckit/utils/Compressor.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit { namespace test { //---------------------------------------------------------------------------------------------------------------------- template size_t timeCompress(const Compressor& compressor, eckit::Buffer& inBuffer, eckit::Buffer& outBuffer, eckit::Timer& timer) { timer.start(); size_t out; for (int i = 0; i < N; ++i) { out = compressor.compress(inBuffer, inBuffer.size(), outBuffer); } timer.stop(); std::cout << " - compress() rate " << Bytes(N * inBuffer.size(), timer) << " -- factor " << std::fixed << std::setprecision(2) << ((100.0 * out) / inBuffer.size()) << "% -- "; std::cout << std::fixed << std::setprecision(1) << (out / 1024.0) << "/" << (inBuffer.size() / 1024.0) << " KB" << std::endl; return out; } template void timeDecompress(const Compressor& compressor, eckit::Buffer& inBuffer, size_t inlen, eckit::Buffer& outBuffer, size_t outlen, eckit::Timer& timer) { timer.start(); for (int i = 0; i < N; ++i) { compressor.uncompress(inBuffer, inlen, outBuffer, outlen); } timer.stop(); std::cout << " - decompress() rate " << Bytes(N * outlen, timer) << std::endl; } struct BinaryData { eckit::Buffer in; std::string description; BinaryData(const eckit::PathName& path, const std::string& desc) : in(path.size()), description(desc) { std::unique_ptr dh(path.fileHandle()); dh->openForRead(); dh->read(in, in.size()); dh->close(); } }; //---------------------------------------------------------------------------------------------------------------------- CASE("Test compression performance") { eckit::Timer timer; std::vector data; data.emplace_back("2t_sfc.grib", "GRIB t2 surface layer"); data.emplace_back("2t_sfc_regrid.grib", "GRIB t2 surface layer re-gridded"); data.emplace_back("vo-d_6ml.grib", "GRIB vo/d layers (10-15 spherical harmonics)"); data.emplace_back("u-v_6ml.grib", "GRIB u/v layers (10-15)"); data.emplace_back("q_6ml_regrid.grib", "GRIB q 6 layers (10-15) re-gridded"); std::vector compressors{"none", "lz4", "snappy", "aec", "bzip2"}; constexpr int N = 5; // Number of iterations to use for each case auto test_case = [&](const Compressor& compressor, BinaryData& data) { // Allocations here to prevent them from being timed in timings during first iteration // Allocation with more than required can prevent internal buffers and memcopies, depending on implementation auto ulen = data.in.size(); size_t oversize = size_t(1.2 * ulen); // used in AEC Buffer compressed{oversize}; Buffer uncompressed{oversize}; // touch memory compressed.zero(); uncompressed.zero(); // Compress size_t clen = timeCompress(compressor, data.in, compressed, timer); // Decompress timeDecompress(compressor, compressed, clen, uncompressed, ulen, timer); }; for (const auto& name : compressors) { if (eckit::CompressorFactory::instance().has(name)) { std::cout << name << std::endl; std::unique_ptr compressor(eckit::CompressorFactory::instance().build(name)); for (auto& d : data) { std::cout << " " << d.description << std::endl; test_case(*compressor, d); } } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace test } // namespace eckit int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_regex.cc0000664000175000017500000000153315253173651017773 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/eckit.h" #include "eckit/exception/Exceptions.h" #include "eckit/utils/Regex.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Escape special regex chars in string") { EXPECT(Regex::escape(".^$*+-?()[]{}\\|") == "\\.\\^\\$\\*\\+\\-\\?\\(\\)\\[\\]\\{\\}\\\\\\|"); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_byteswap.cc0000664000175000017500000002374115253173651020524 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/log/Log.h" #include "eckit/types/Types.h" #include "eckit/utils/ByteSwap.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Low-level roundtrip 16 bits") { uint16_t v = 123; uint16_t s = eckit::bitswap16(v); // std::cout << s << std::endl; EXPECT(s == 31488); uint16_t r = eckit::bitswap16(s); EXPECT(r == v); } CASE("Low-level roundtrip 32 bits") { uint32_t v = 1234; uint32_t s = eckit::bitswap32(v); std::cout << s << std::endl; EXPECT(s == 3523477504); uint32_t r = eckit::bitswap32(s); EXPECT(r == v); } CASE("Low-level roundtrip 64 bits") { uint64_t v = 12345; uint64_t s = eckit::bitswap64(v); std::cout << s << std::endl; EXPECT(s == 4120793659044003840); uint64_t r = eckit::bitswap64(s); EXPECT(r == v); } //---------------------------------------------------------------------------------------------------------------------- #if eckit_LITTLE_ENDIAN // use htons and htonl as correctness tests, and test against specifc bit patterns CASE("Check correctness 16 bit swap") { uint16_t v = 511; std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "0000000111111111"); uint16_t r = htons(v); std::cout << r << " = " << bits_to_str(r) << std::endl; EXPECT_EQUAL(bits_to_str(r), "1111111100000001"); eckit::byteswap(v); std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "1111111100000001"); } CASE("Check correctness 16 bit swap") { // special case of symmetric swap // std::string bitstr("1010101010101010"); // 43690 // std::bitset<16> s(bitstr); // unsigned long v = s.to_ulong(); // std::cout << v << " = " << bits_to_str(v) << std::endl; uint16_t v = 43690; std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "1010101010101010"); uint16_t r = htons(v); std::cout << r << " = " << bits_to_str(r) << std::endl; EXPECT_EQUAL(bits_to_str(r), "1010101010101010"); byteswap(v); std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "1010101010101010"); } CASE("Check correctness 16 bit swap") { // special case of anti-symmetric swap // std::string bitstr("0101010110101010"); // 21930 // std::bitset<16> s(bitstr); // unsigned long v = s.to_ulong(); // std::cout << v << " = " << bits_to_str(v) << std::endl; uint16_t v = 21930; std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "0101010110101010"); uint16_t r = htons(v); std::cout << r << " = " << bits_to_str(r) << std::endl; EXPECT_EQUAL(bits_to_str(r), "1010101001010101"); eckit::byteswap(v); std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "1010101001010101"); } CASE("Check correctness 32 bit swap") { uint32_t v = 2212345511; std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "10000011110111011011011010100111"); uint32_t r = htonl(v); std::cout << r << " = " << bits_to_str(r) << std::endl; EXPECT_EQUAL(bits_to_str(r), "10100111101101101101110110000011"); eckit::byteswap(v); std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT_EQUAL(bits_to_str(v), "10100111101101101101110110000011"); } CASE("Check correctness 64 bit swap") { union u64 { uint64_t v; struct { uint32_t hi; uint32_t lo; } s; }; u64 v; v.v = 891067242212345511; std::cout << v.v << " = " << bits_to_str(v.v) << std::endl; EXPECT_EQUAL(bits_to_str(v.v), "0000110001011101101101001110111110001011000011011100011010100111"); uint32_t r_hi = htonl(v.s.hi); uint32_t r_lo = htonl(v.s.lo); std::cout << bits_to_str(r_hi) + bits_to_str(r_lo) << std::endl; EXPECT_EQUAL(bits_to_str(r_hi) + bits_to_str(r_lo), "1010011111000110000011011000101111101111101101000101110100001100"); #if defined(htonll) uint64_t sr = htonll(v.v); std::cout << bits_to_str(sr) << std::endl; EXPECT_EQUAL(bits_to_str(sr), "1010011111000110000011011000101111101111101101000101110100001100"); #endif eckit::byteswap(v.v); std::cout << bits_to_str(v.v) << std::endl; EXPECT_EQUAL(bits_to_str(v.v), "1010011111000110000011011000101111101111101101000101110100001100"); } //---------------------------------------------------------------------------------------------------------------------- template void test_scalar_value(T v, std::string bitstr, std::string swapped) { T ref = v; std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT(bits_to_str(v) == bitstr); eckit::byteswap(v); std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT(bits_to_str(v) == swapped); eckit::byteswap(v); std::cout << v << " = " << bits_to_str(v) << std::endl; EXPECT(bits_to_str(v) == bitstr); EXPECT(ref == v); } CASE("ByteSwap short") { SECTION("short 1234") { test_scalar_value(1234, "0000010011010010", "1101001000000100"); } SECTION("short 3145") { test_scalar_value(3145, "0000110001001001", "0100100100001100"); } SECTION("short 32767") { test_scalar_value(32767, "0111111111111111", "1111111101111111"); } } CASE("ByteSwap int") { SECTION("int 2212345511") { test_scalar_value(212345511, "00001100101010000010001010100111", "10100111001000101010100000001100"); } SECTION("int 1024*1024") { test_scalar_value(1024 * 1024, "00000000000100000000000000000000", "00000000000000000001000000000000"); } } CASE("ByteSwap long") { SECTION("long 7") { test_scalar_value(7, "0000000000000000000000000000000000000000000000000000000000000111", "0000011100000000000000000000000000000000000000000000000000000000"); } SECTION("long 870633123454325131") { test_scalar_value(870633123454325131, "0000110000010101000111000011011010000101001011110001000110001011", "1000101100010001001011111000010100110110000111000001010100001100"); } } CASE("ByteSwap double") { SECTION("double exact integer 7.0") { test_scalar_value(7.0, "0100000000011100000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000001110001000000"); } SECTION("double PI") { test_scalar_value(M_PI, "0100000000001001001000011111101101010100010001000010110100011000", "0001100000101101010001000101010011111011001000010000100101000000"); } SECTION("double max") { test_scalar_value(std::numeric_limits::max(), "0111111111101111111111111111111111111111111111111111111111111111", "1111111111111111111111111111111111111111111111111110111101111111"); } } #endif // eckit_LITTLE_ENDIAN //---------------------------------------------------------------------------------------------------------------------- template std::vector build_reference() { std::vector ref(1024); for (size_t i = 0; i < ref.size(); ++i) { ref[i] = T(i); } return ref; } template std::ostream& print_bits(std::ostream& out, const std::vector& v) { for (size_t i = 0; i < v.size(); ++i) { out << bits_to_str(v[i]) << "\n"; } out << std::endl; return out; } template void test_roundtrip_vector() { // std::cout << "sizeof(T)*8 " << sizeof(T)*8 << std::endl; const auto ref = build_reference(); // std::cout << "reference" << std::endl; print_bits(std::cout, ref); auto v = ref; // std::cout << "v initial" << std::endl; print_bits(std::cout, v); // First roundtrip with array API eckit::byteswap(v); // std::cout << "v swapped" << std::endl; print_bits(std::cout, v); // std::cout << "v[1/2] " << v[v.size() / 2] << std::endl; EXPECT(v != ref); const auto swap = v; // save swaped state eckit::byteswap(v.data(), v.size()); // std::cout << "v back" << std::endl; print_bits(std::cout, v); EXPECT(v == ref); // Second roundtrip with scalar API for (auto& x : v) { eckit::byteswap(x); } // std::cout << "v elem swapped" << std::endl; print_bits(std::cout, v); EXPECT(v != ref); EXPECT(v == swap); eckit::byteswap(v); // std::cout << "v elem swapped back" << std::endl; print_bits(std::cout, v); EXPECT(v == ref); } CASE("test_roundtrip_vector") { SECTION("short") { test_roundtrip_vector(); } SECTION("unsigned short") { test_roundtrip_vector(); } SECTION("int") { test_roundtrip_vector(); } SECTION("unsigned int") { test_roundtrip_vector(); } SECTION("long") { test_roundtrip_vector(); } SECTION("unsigned long") { test_roundtrip_vector(); } SECTION("long long") { test_roundtrip_vector(); } SECTION("unsigned long long") { test_roundtrip_vector(); } SECTION("float") { test_roundtrip_vector(); } SECTION("double") { test_roundtrip_vector(); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_safe_casts.cc0000664000175000017500000001131515253173651020773 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/exception/Exceptions.h" #include "eckit/testing/Test.h" #include "eckit/utils/SafeCasts.h" #include #include using eckit::into_signed; using eckit::into_unsigned; CASE("Can convert positive signed to unsigned") { EXPECT_EQUAL(into_unsigned(int8_t{5}), uint8_t{5}); EXPECT_EQUAL(into_unsigned(int16_t{5}), uint16_t{5}); EXPECT_EQUAL(into_unsigned(int32_t{5}), uint32_t{5}); EXPECT_EQUAL(into_unsigned(int64_t{5}), uint64_t{5}); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); } CASE("Throws 'BadCast' on negative signed to unsigned") { EXPECT_THROWS_AS((void)into_unsigned(int8_t{-5}), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(int16_t{-5}), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(int32_t{-5}), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(int64_t{-5}), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(static_cast(-5)), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(static_cast(-5)), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(static_cast(-5)), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(static_cast(-5)), eckit::BadCast); EXPECT_THROWS_AS((void)into_unsigned(static_cast(-5)), eckit::BadCast); } CASE("No-op if used as unsigned to unsigned cast") { EXPECT_EQUAL(into_unsigned(uint8_t{5}), 5); EXPECT_EQUAL(into_unsigned(uint16_t{5}), 5); EXPECT_EQUAL(into_unsigned(uint32_t{5}), 5); EXPECT_EQUAL(into_unsigned(uint64_t{5}), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); EXPECT_EQUAL(into_unsigned(static_cast(5)), 5); } CASE("Can convert unsigned to signed for unsigned values smaller that 2^(bits-1)-1") { EXPECT_EQUAL(into_signed(uint8_t{5}), int8_t{5}); EXPECT_EQUAL(into_signed(uint16_t{5}), int16_t{5}); EXPECT_EQUAL(into_signed(uint32_t{5}), int32_t{5}); EXPECT_EQUAL(into_signed(uint64_t{5}), int64_t{5}); EXPECT_EQUAL(into_signed(static_cast(5)), 5); EXPECT_EQUAL(into_signed(static_cast(5)), 5); EXPECT_EQUAL(into_signed(static_cast(5)), 5); EXPECT_EQUAL(into_signed(static_cast(5)), 5); EXPECT_EQUAL(into_signed(static_cast(5)), 5); } CASE("Throws 'BadCast' on unsigned to signed conversion if value cannot be represented") { EXPECT_THROWS_AS((void)into_signed(uint8_t{std::numeric_limits::max()}), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(uint16_t{std::numeric_limits::max()}), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(uint32_t{std::numeric_limits::max()}), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(uint64_t{std::numeric_limits::max()}), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(static_cast(std::numeric_limits::max())), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(static_cast(std::numeric_limits::max())), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(static_cast(std::numeric_limits::max())), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(static_cast(std::numeric_limits::max())), eckit::BadCast); EXPECT_THROWS_AS((void)into_signed(static_cast(std::numeric_limits::max())), eckit::BadCast); } CASE("No-op if used as signed to signed cast") { EXPECT_EQUAL(into_signed(int8_t{-5}), -5); EXPECT_EQUAL(into_signed(int16_t{-5}), -5); EXPECT_EQUAL(into_signed(int32_t{-5}), -5); EXPECT_EQUAL(into_signed(int64_t{-5}), -5); EXPECT_EQUAL(into_signed(static_cast(-5)), -5); EXPECT_EQUAL(into_signed(static_cast(-5)), -5); EXPECT_EQUAL(into_signed(static_cast(-5)), -5); EXPECT_EQUAL(into_signed(static_cast(-5)), -5); EXPECT_EQUAL(into_signed(static_cast(-5)), -5); } int main(int argc, char* argv[]) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_compressor.cc0000664000175000017500000000772615253173651021067 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/io/Buffer.h" #include "eckit/utils/Compressor.h" #include "eckit/utils/MD5.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { static std::string msg("THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG'S BACK 1234567890"); static std::vector compressions{"none", "snappy", "lz4", "bzip2", "aec"}; //---------------------------------------------------------------------------------------------------------------------- void EXPECT_compress_uncompress_1(Compressor& c, const Buffer& in, size_t ulen) { // Buffers are not allocated. The compress/uncompress will do so as required Buffer compressed; Buffer uncompressed; size_t clen = c.compress(in, ulen, compressed); c.uncompress(compressed, clen, uncompressed, ulen); EXPECT(std::memcmp(uncompressed, in, ulen) == 0); } void EXPECT_compress_uncompress_2(Compressor& c, const Buffer& in, size_t ulen) { // Buffers are pre-allocated. This may allow implementation dependent optimizations Buffer compressed(size_t(1.2 * ulen)); Buffer uncompressed(size_t(1.2 * ulen)); size_t clen = c.compress(in, ulen, compressed); c.uncompress(compressed, clen, uncompressed, ulen); EXPECT(std::memcmp(uncompressed, in, ulen) == 0); } void EXPECT_reproducible_compression(Compressor& c, size_t times) { std::vector reproduce_lengths; std::vector reproduce_checksum; for (size_t i = 0; i < times + 1; ++i) { Buffer uncompressed(msg.data(), msg.size()); Buffer compressed; size_t compressed_size = c.compress(uncompressed, uncompressed.size(), compressed); reproduce_lengths.emplace_back(compressed_size); reproduce_checksum.emplace_back(eckit::MD5(compressed, compressed_size)); } const auto& ref_length = reproduce_lengths.front(); bool reproducible_length{true}; for (auto& length : reproduce_lengths) { if (length != ref_length) { reproducible_length = false; } } EXPECT(reproducible_length); const auto& ref_checksum = reproduce_checksum.front(); bool reproducible_checksum{true}; for (auto& checksum : reproduce_checksum) { if (checksum != ref_checksum) { reproducible_checksum = false; } } EXPECT(reproducible_checksum); } CASE("Builders") { std::unique_ptr c; SECTION("CASE No Compression - case insensitive") { EXPECT_NO_THROW(c.reset(CompressorFactory::instance().build("nOnE"))); } SECTION("Not Existing Compression") { EXPECT_THROWS(c.reset(CompressorFactory::instance().build("dummy name"))); } } CASE("Compression") { Buffer in(2 * msg.size()); // oversized on purpose in.copy(msg.c_str(), msg.size()); const size_t len = msg.size(); // valid size std::unique_ptr c; SECTION("CASE Default Compression") { EXPECT_NO_THROW(c.reset(CompressorFactory::instance().build())); EXPECT_compress_uncompress_1(*c, in, len); EXPECT_compress_uncompress_2(*c, in, len); } for (const auto& compression : CompressorFactory::instance().keys()) { SECTION("CASE " + compression) { if (CompressorFactory::instance().has(compression)) { EXPECT_NO_THROW(c.reset(CompressorFactory::instance().build(compression))); EXPECT_compress_uncompress_1(*c, in, len); EXPECT_compress_uncompress_2(*c, in, len); EXPECT_reproducible_compression(*c, 10); } } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_translator.cc0000664000175000017500000001150415253173651021051 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/eckit.h" #include "eckit/exception/Exceptions.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Translator.h" #include "eckit/filesystem/PathName.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Translate strings to integers") { Translator toSize; EXPECT(toSize("0") == size_t(0)); EXPECT(toSize("1") == size_t(1)); EXPECT(toSize("100") == size_t(100)); EXPECT(toSize("1KB") == size_t(1024)); EXPECT(toSize("1MB") == size_t(1024 * 1024)); EXPECT(toSize("1GB") == size_t(1024 * 1024 * 1024)); // EXPECT( toSize("1TB") == size_t(1024*1024*1024*1024)); // EXPECT( toSize("1PB") == size_t(1024*1024*1024*1024*1024)); // EXPECT( toSize("1EB") == size_t(1024*1024*1024*1024*1024*1024)); EXPECT(toSize("1KiB") == size_t(1024)); EXPECT(toSize("1MiB") == size_t(1024 * 1024)); EXPECT(toSize("1GiB") == size_t(1024 * 1024 * 1024)); std::cout << toSize("1GiB") << std::endl; std::cout << toSize("1TiB") << std::endl; EXPECT(toSize("1TiB") == size_t(1LL << 40)); EXPECT(toSize("1PiB") == size_t(1LL << 50)); EXPECT(toSize("1EiB") == size_t(1LL << 60)); // Translator toInt; // Translator toUInt; // Translator toLong; // Translator toULong; } CASE("Translate strings to double") { double half = 0.5; double zero = 0.0; double hund = 100.0; double ten = 10.; Translator t; EXPECT(half == t("0.5")); EXPECT(hund == t("1e2")); EXPECT(hund == t("1E2")); EXPECT(hund == t("1.e2")); EXPECT(hund == t("1.E2")); EXPECT(hund == t("1.0e2")); EXPECT(hund == t("1.0E2")); EXPECT(hund == t("1e002")); EXPECT(hund == t("1E002")); EXPECT(hund == t("1.0e002")); EXPECT(hund == t("1.0E002")); EXPECT(zero == t("0")); EXPECT(zero == t("0.")); EXPECT(zero == t("0.0")); EXPECT(zero == t("0.00")); EXPECT(zero == t("0.0e0")); EXPECT(zero == t("0.0e-0")); EXPECT(zero == t("0.0e+0")); EXPECT(zero == t("+0")); EXPECT(zero == t("+0.")); EXPECT(zero == t("+0.0")); EXPECT(zero == t("+0.00")); EXPECT(zero == t("+0.0e0")); EXPECT(zero == t("+0.0e-0")); EXPECT(zero == t("+0.0e+0")); EXPECT(zero == t("-0")); EXPECT(zero == t("-0.")); EXPECT(zero == t("-0.0")); EXPECT(zero == t("-0.00")); EXPECT(zero == t("-0.0e0")); EXPECT(zero == t("-0.0e-0")); EXPECT(zero == t("-0.0e+0")); /// weird cases that actually pass EXPECT_NO_THROW(t("inf")); // inf is acceptable, case insensitive EXPECT_NO_THROW(t("INF")); // INF is acceptable, case insensitive EXPECT_NO_THROW(t("infinity")); // infinity is acceptable, case insensitive EXPECT_NO_THROW(t("INFINITY")); // INFINITY is acceptable, case insensitive EXPECT_NO_THROW(t("nan")); // nan is acceptable, case insensitive EXPECT_NO_THROW(t("NAN")); // NAN is acceptable, case insensitive EXPECT(zero == t("0x0")); // hexadecimal is acceptable EXPECT(ten == t("0xA")); // hexadecimal is acceptable /// these should fail ... EXPECT_THROWS_AS(t(""), BadParameter); // empty string EXPECT_THROWS_AS(t("0.5 "), BadParameter); // no spaces accepted -- we are being extra strict EXPECT_THROWS_AS(t(" 0.5"), BadParameter); // no spaces accepted -- we are being extra strict EXPECT_THROWS_AS(t(" 0.5 "), BadParameter); // no spaces accepted -- we are being extra strict EXPECT_THROWS_AS(t("1e+10000"), BadParameter); // overflow, max ~ 1.79769e+308 EXPECT_THROWS_AS(t("1e-10000"), BadParameter); // underflow, min ~ 2.22507e-308 EXPECT_THROWS_AS(t("0.5a"), BadParameter); EXPECT_THROWS_AS(t("foobar"), BadParameter); EXPECT_THROWS_AS(t("foo555bar"), BadParameter); } CASE("Translate signed char as a number") { Translator t; EXPECT(t((signed char)127) == std::string("127")); EXPECT(t((signed char)-127) == std::string("-127")); EXPECT(t((signed char)128) == std::string("-128")); } CASE("Translate through construction (including implicit conversion)") { Translator t; EXPECT(t("123") == 123); EXPECT(t(eckit::PathName("123")) == 123); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_literals.cc0000664000175000017500000000117715253173651020504 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/utils/Literals.h" #include "eckit/testing/Test.h" using namespace eckit::literals; using namespace eckit::testing; namespace eckit::test { CASE("size literals") { EXPECT_EQUAL(1_KiB, 1ULL << 10); EXPECT_EQUAL(1_MiB, 1ULL << 20); EXPECT_EQUAL(1_GiB, 1ULL << 30); EXPECT_EQUAL(1_TiB, 1ULL << 40); EXPECT_EQUAL(1_PiB, 1ULL << 50); EXPECT_EQUAL(1_EiB, 1ULL << 60); } } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_hashing.cc0000664000175000017500000002026115253173651020301 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/utils/Hash.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit { namespace test { //---------------------------------------------------------------------------------------------------------------------- std::vector tests = {"", "a", "abc", "message digest", "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "12345678901234567890123456789012345678901234567890123456789012345678901234567890", "The quick brown fox jumps over the lazy cog"}; std::map> results = { {"nOnE", { "", //"" "", //"a" "", //"abc" "", //"message digest" "", //"abcdefghijklmnopqrstuvwxyz" "", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "", //"The quick brown fox jumps over the lazy cog" "", //"The quick brown fox jumps over the lazy cog" x 2 }}, {"md4", { "31d6cfe0d16ae931b73c59d7e0c089c0", //"" "bde52cb31de33e46245e05fbdbd6fb24", //"a" "a448017aaf21d8525fc10ae87aa6729d", //"abc" "d9130a8164549fe818874806e1c7014b", //"message digest" "d79e1c308aa5bbcdeea8ed63df412da9", //"abcdefghijklmnopqrstuvwxyz" "043f8582f241db351ce627e153e7f0e4", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "e33b4ddc9c38f2199c3e7b164fcc0536", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "b86e130ce7028da59e672d56ad0113df", //"The quick brown fox jumps over the lazy cog" "16b52abf32d2796d53c6202a877a69a0", //"The quick brown fox jumps over the lazy cog" x 2 }}, {"MD4", { "31d6cfe0d16ae931b73c59d7e0c089c0", //"" "bde52cb31de33e46245e05fbdbd6fb24", //"a" "a448017aaf21d8525fc10ae87aa6729d", //"abc" "d9130a8164549fe818874806e1c7014b", //"message digest" "d79e1c308aa5bbcdeea8ed63df412da9", //"abcdefghijklmnopqrstuvwxyz" "043f8582f241db351ce627e153e7f0e4", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "e33b4ddc9c38f2199c3e7b164fcc0536", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "b86e130ce7028da59e672d56ad0113df", //"The quick brown fox jumps over the lazy cog" "16b52abf32d2796d53c6202a877a69a0", //"The quick brown fox jumps over the lazy cog" x 2 }}, {"MD5", { "d41d8cd98f00b204e9800998ecf8427e", //"" "0cc175b9c0f1b6a831c399e269772661", //"a" "900150983cd24fb0d6963f7d28e17f72", //"abc" "f96b697d7cb7938d525a2f31aaf161d0", //"message digest" "c3fcd3d76192e4007dfb496cca67e13b", //"abcdefghijklmnopqrstuvwxyz" "d174ab98d277d9f5a5611c2c9f419d9f", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "57edf4a22be3c955ac49da2e2107b67a", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "1055d3e698d289f2af8663725127bd4b", //"The quick brown fox jumps over the lazy cog" "f6c40218ba016d723a32a43e3c96a929", //"The quick brown fox jumps over the lazy cog" x 2 }}, {"SHA1", { "da39a3ee5e6b4b0d3255bfef95601890afd80709", //"" "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", //"a" "a9993e364706816aba3e25717850c26c9cd0d89d", //"abc" "c12252ceda8be8994d5fa0290a47231c1d16aae3", //"message digest" "32d10c7b8cf96570ca04ce37f2a19d84240d3a89", //"abcdefghijklmnopqrstuvwxyz" "761c457bf73b14d27e9e9265c46f4b4dda11f940", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "50abf5706a150990a08b2c5ea40fa0e585554732", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3", //"The quick brown fox jumps over the lazy cog" "6f020371ced46a2989a2161a113f34ccd35e0729", //"The quick brown fox jumps over the lazy cog" x 2 }}, {"xxh64", { "ef46db3751d8e999", //"" "d24ec4f1a98c6e5b", //"a" "44bc2cf5ad770999", //"abc" "066ed728fceeb3be", //"message digest" "cfe1f278fa89835c", //"abcdefghijklmnopqrstuvwxyz" "aaa46907d3047814", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "e04a477f19ee145d", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "2dcf47703493b6ca", //"The quick brown fox jumps over the lazy cog" "e32a7da747f1bd6e", //"The quick brown fox jumps over the lazy cog" x 2 }}, {"xxhash", // Deprecated alias of xxh64 { "ef46db3751d8e999", //"" "d24ec4f1a98c6e5b", //"a" "44bc2cf5ad770999", //"abc" "066ed728fceeb3be", //"message digest" "cfe1f278fa89835c", //"abcdefghijklmnopqrstuvwxyz" "aaa46907d3047814", //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" "e04a477f19ee145d", //"12345678901234567890123456789012345678901234567890123456789012345678901234567890" "2dcf47703493b6ca", //"The quick brown fox jumps over the lazy cog" "e32a7da747f1bd6e", //"The quick brown fox jumps over the lazy cog" x 2 }}, }; void testHash(Hash& hash, std::string hashName) { auto hashResults = results.find(hashName); if (hashResults == results.end()) return; for (int i = 0; i < tests.size(); i++) { std::string testResults = hashResults->second[i]; // if (testResults.empty()) // break; hash.reset(); hash.add(tests[i].c_str(), tests[i].size()); EXPECT(testResults == hash.digest()); // constructor-based initialization std::unique_ptr hashInitialized{HashFactory::instance().build(hashName, tests[i])}; EXPECT(testResults == hashInitialized->digest()); // initialize with dummy data and test reset hashInitialized.reset(HashFactory::instance().build(hashName, "dummy data")); hashInitialized->reset(); hashInitialized->add(tests[i].c_str(), tests[i].size()); EXPECT(testResults == hashInitialized->digest()); } // test incremental add { std::string testResults = hashResults->second[tests.size()]; hash.reset(); hash.add(tests[tests.size() - 1].c_str(), tests[tests.size() - 1].size()); hash.add(tests[tests.size() - 1].c_str(), tests[tests.size() - 1].size()); EXPECT(testResults == hash.digest()); // test two adds (as before) is same as a combination of one compute std::string combined; combined += tests[tests.size() - 1]; combined += tests[tests.size() - 1]; EXPECT(hash.compute(combined.c_str(), combined.size()) == testResults); } } CASE("Hashing") { std::unique_ptr hash; SECTION("Default Hashing") { EXPECT_NO_THROW(hash.reset(HashFactory::instance().build())); testHash(*hash, "MD5"); } SECTION("Not Existing Hashing") { EXPECT_THROWS(HashFactory::instance().build("dummy name")); } for (auto const& element : results) { if (HashFactory::instance().has(element.first)) { SECTION(element.first + " Hashing") { EXPECT_NO_THROW(hash.reset(HashFactory::instance().build(element.first))); testHash(*hash, element.first); } } } } //---------------------------------------------------------------------------------------------------------------------- } // end namespace test } // end namespace eckit int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_rle.cc0000664000175000017500000000774215253173651017453 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include "eckit/config/LibEcKit.h" #include "eckit/log/Timer.h" #include "eckit/utils/RLE.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- void test(const std::vector& in, EncodingClock::duration timeout = std::chrono::milliseconds(2), long depth = -1, long expectedSize = -1) { std::vector out; std::vector outTimeout; int maxLoop = 5000; long long n; long long t; { eckit::Timer timer; n = RLEencode2(in.begin(), in.end(), std::back_inserter(out), maxLoop); LOG_DEBUG_LIB(LibEcKit) << "no_timeout - time elapsed: " << timer.elapsed() << " - output size: " << out.size() << std::endl; } { eckit::Timer timer; t = RLEencode2(in.begin(), in.end(), std::back_inserter(outTimeout), maxLoop, timeout, depth); LOG_DEBUG_LIB(LibEcKit) << "timeout " << std::chrono::duration_cast(timeout).count() << "ms - time elapsed: " << timer.elapsed() << " - output size: " << outTimeout.size() << std::endl; } if (expectedSize > 0) { EXPECT_EQUAL(expectedSize, n); EXPECT_EQUAL(expectedSize, t); EXPECT_EQUAL(out.size(), outTimeout.size()); for (size_t i = 0; i < out.size(); i++) { EXPECT_EQUAL(out[i], outTimeout[i]); } } std::vector decoded; RLEdecode2(out.begin(), out.end(), std::back_inserter(decoded)); EXPECT_EQUAL(in.size(), decoded.size()); for (size_t i = 0; i < in.size(); i++) { EXPECT_EQUAL(in[i], decoded[i]); } std::vector decodedTimeout; RLEdecode2(outTimeout.begin(), outTimeout.end(), std::back_inserter(decodedTimeout)); EXPECT_EQUAL(in.size(), decodedTimeout.size()); for (size_t i = 0; i < in.size(); i++) { EXPECT_EQUAL(in[i], decodedTimeout[i]); } } CASE("single") { std::vector in = {4}; test(in, std::chrono::seconds(1), 10, 1); } CASE("identical") { size_t size = 1000; std::vector in; in.reserve(size); for (size_t i = 0; i < size; i++) { in.push_back(4); } test(in, std::chrono::seconds(1), 10, 2); /// we expect [-1000,4] } CASE("interleaved") { size_t size = 1000; std::vector in; in.reserve(size); for (size_t i = 0; i < size; i++) { in.push_back(i % 2); } test(in, std::chrono::seconds(1), 10, 4); /// we expect [-500,-2,0,1] } CASE("pattern") { size_t size = 100000; std::vector in; in.reserve(size); for (size_t i = 0; i < size; i++) { in.push_back(std::ceil(std::sqrt(i))); } test(in, std::chrono::milliseconds(50), 1000); } void randTest(int limit, int size) { std::random_device rd; // a seed source for the random number engine std::mt19937 gen(rd()); // mersenne_twister_engine seeded with rd() std::uniform_int_distribution<> distrib(1, limit); std::vector in; in.reserve(size); for (size_t i = 0; i < size; i++) { in.push_back(distrib(gen)); } test(in, std::chrono::seconds(2), 1000); } CASE("randMaxInt") { randTest(std::numeric_limits::max(), 10000000); } CASE("rand100") { randTest(100, 10000000); } CASE("rand10") { randTest(10, 1000000); } CASE("rand2") { randTest(2, 1000000); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/hash-performance.cc0000664000175000017500000000456115253173651021050 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/io/Buffer.h" #include "eckit/log/Bytes.h" #include "eckit/log/Seconds.h" #include "eckit/log/Timer.h" #include "eckit/utils/Hash.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit { namespace test { //---------------------------------------------------------------------------------------------------------------------- template void timeAdd(Hash& hash, eckit::Buffer& buffer, eckit::Timer& timer) { timer.start(); for (int i = 0; i < N; ++i) { hash.reset(); for (size_t j = 0; j < M; ++j) { hash.add(buffer, buffer.size()); } hash.digest(); } timer.stop(); std::cout << " - " << N << " x " << M << " x add(" << Bytes(buffer.size()) << ") rate " << Bytes(N * M * buffer.size(), timer) << std::endl; } template void timeCompute(Hash& hash, eckit::Buffer& buffer, eckit::Timer& timer) { timer.start(); for (int i = 0; i < N; ++i) { std::string s = hash.compute(buffer, buffer.size()); assert(!s.empty()); } timer.stop(); std::cout << " - " << N << " x compute(" << Bytes(buffer.size()) << ") rate " << Bytes(N * buffer.size(), timer) << std::endl; } //---------------------------------------------------------------------------------------------------------------------- CASE("Test hash performance") { eckit::Buffer buffer(4 * 1024 * 1024); eckit::Buffer buffer2(64 * 1024 * 1024); eckit::Timer timer; std::vector hashes{"xxh64", "MD4", "MD5", "SHA1"}; for (auto& name : hashes) { if (eckit::HashFactory::instance().has(name)) { std::cout << name << std::endl; std::unique_ptr hash(eckit::HashFactory::instance().build(name)); timeAdd<20, 1>(*hash, buffer, timer); timeCompute<5>(*hash, buffer2, timer); } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace test } // namespace eckit int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_semanticversion.cc0000664000175000017500000000673315253173651022101 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/log/Log.h" #include "eckit/runtime/Tool.h" #include "eckit/types/SemanticVersion.h" #include "eckit/types/Types.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Empty versions") { SemanticVersion v; EXPECT(std::string(v) == "0.0.0"); SemanticVersion v0("0.0.0"); SemanticVersion v1("1.0.0"); SemanticVersion v2("0.1.0"); SemanticVersion v3("0.0.1"); EXPECT(v == v0); EXPECT(v < v1); EXPECT(v < v2); EXPECT(v < v3); } CASE("Construct with integers") { EXPECT(SemanticVersion() == SemanticVersion(0, 0, 0)); EXPECT(SemanticVersion("0.0.0") == SemanticVersion(0, 0, 0)); EXPECT(SemanticVersion("27.125.22") == SemanticVersion(27, 125, 22)); EXPECT(SemanticVersion("2.1.3") != SemanticVersion(27, 125, 22)); } CASE("Failed construction") { EXPECT_THROWS_AS(SemanticVersion("1"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("2.3"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.2.3.4"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.2-4"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.2.4-r1"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.-2.4"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.2.456abc"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.aaa.56"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("v1.2.4"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("x.y.z"), eckit::BadValue); EXPECT_THROWS_AS(SemanticVersion("1.2.999999999999999999999999999999"), eckit::OutOfRange); } CASE("Version comparison") { std::ostringstream ss; ss << SemanticVersion("27.125.22"); EXPECT(ss.str() == "27.125.22"); } CASE("Version comparison") { SemanticVersion v1("1.0.0"); SemanticVersion v2("0.1.0"); SemanticVersion v3("0.0.1"); EXPECT(std::string(v1) == "1.0.0"); EXPECT(std::string(v2) == "0.1.0"); EXPECT(std::string(v3) == "0.0.1"); EXPECT(v1 > v2); EXPECT(v1 > v3); EXPECT(v2 < v1); EXPECT(v2 > v3); EXPECT(v3 < v1); EXPECT(v3 < v2); } CASE("Multidigit versions") { SemanticVersion v("27.125.22"); EXPECT(std::string(v) == "27.125.22"); EXPECT(v == SemanticVersion("27.125.22")); EXPECT(v != SemanticVersion("27.125.33")); EXPECT(v != SemanticVersion("27.25.22")); EXPECT(v != SemanticVersion("7.125.22")); EXPECT(v > SemanticVersion("27.125.11")); EXPECT(v > SemanticVersion("27.1.33")); EXPECT(v > SemanticVersion("2.125.2")); EXPECT(v < SemanticVersion("27.125.33")); EXPECT(v < SemanticVersion("27.777.33")); EXPECT(v < SemanticVersion("42.125.22")); EXPECT(v >= SemanticVersion("27.125.22")); EXPECT(v >= SemanticVersion("27.125.12")); EXPECT(v <= SemanticVersion("27.125.22")); EXPECT(v <= SemanticVersion("27.125.33")); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/utils/test_rendezvoushash.cc0000664000175000017500000001346115253173651021734 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/types/Types.h" #include "eckit/utils/RendezvousHash.h" #include "eckit/utils/Translator.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_utils_rendezvous_hash_constructor") { Log::info() << Here() << std::endl; std::vector nodes = {"node01", "node02", "node03", "node04"}; eckit::RendezvousHash rendezvous(nodes, &RendezvousHash::md5); std::map dict; dict["class"] = "od"; dict["stream"] = "oper"; dict["type"] = "fc"; dict["level"] = "1"; std::vector indices; std::vector node_order; rendezvous.hashOrder(dict, indices); rendezvous.hashOrder(dict, node_order); EXPECT(indices.size() == 4); std::sort(indices.begin(), indices.end()); EXPECT(indices == std::vector({0, 1, 2, 3})); EXPECT(nodes.size() == 4); for (const std::string& node : nodes) { EXPECT(std::find(node_order.begin(), node_order.end(), node) != node_order.end()); } } CASE("test_eckit_utils_rendezvous_hash_distribution") { Log::info() << Here() << std::endl; eckit::Translator toStr; std::vector params{"2t", "2d", "z", "u", "v", "130.128", "138.128"}; std::vector nodes = {"node01", "node02", "node03", "node04", "node05", "node06", "node07"}; eckit::RendezvousHash rendezvous(nodes); std::map dict{{"class", "od"}, {"stream", "oper"}, {"type", "fc"}}; std::map counts; std::vector ordered_nodes; for (std::vector::iterator param = params.begin(); param != params.end(); ++param) { dict["param"] = *param; for (size_t step = 0; step < 240; ++step) { dict["step"] = toStr(step); for (size_t level = 0; level < 10; ++level) { dict["level"] = toStr(level); rendezvous.hashOrder(dict, ordered_nodes); EXPECT(ordered_nodes.size() == 7); counts[ordered_nodes[0]]++; } } } // Test that we have << roughly >> similar counts on all the nodes. (Very stochastic). for (const auto& it : counts) { EXPECT(2320 < it.second && 2450 > it.second); } } CASE("test_eckit_utils_rendezvous_hash_empty_dict") { Log::info() << Here() << std::endl; std::map dict; eckit::RendezvousHash rendezvous; rendezvous.addNode("node01"); rendezvous.addNode("node02"); std::vector indices; EXPECT_NO_THROW(rendezvous.hashOrder(dict, indices)); /* don't throw on empty dictionary */ } CASE("test_eckit_utils_rendezvous_hash_throws_empty_node_list") { Log::info() << Here() << std::endl; std::map dict; eckit::RendezvousHash rendezvous(&RendezvousHash::md5); dict["class"] = "od"; dict["stream"] = "oper"; dict["type"] = "fc"; std::vector indices; EXPECT_THROWS_AS(rendezvous.hashOrder(dict, indices), eckit::BadParameter); /* throw on empty node list */ rendezvous.addNode("node01"); rendezvous.addNode("node02"); EXPECT_NO_THROW(rendezvous.hashOrder(dict, indices)); } CASE("test_eckit_utils_rendezvous_hash_add_node") { Log::info() << Here() << std::endl; eckit::Translator toStr; std::vector params{"2t", "2d", "z", "w"}; std::map dict{{"class", "od"}, {"stream", "oper"}, {"type", "fc"}}; eckit::RendezvousHash rendezvous{std::vector({"node01", "node02"})}; const size_t nsteps = 25; const size_t nlevels = 100; std::map counts; std::vector ordered_nodes; for (std::vector::iterator param = params.begin(); param != params.end(); ++param) { dict["param"] = *param; for (size_t step = 0; step < nsteps; ++step) { dict["step"] = toStr(step); for (size_t level = 0; level < nlevels; ++level) { dict["level"] = toStr(level); rendezvous.hashOrder(dict, ordered_nodes); EXPECT(ordered_nodes.size() == 2); counts[ordered_nodes[0]]++; } } } // Test that we have << roughly >> similar counts on all the nodes. (Very stochastic). for (auto kv : counts) { EXPECT(4950 < kv.second && 5050 > kv.second); } std::map counts2; rendezvous.addNode("node03"); rendezvous.addNode("node04"); for (std::vector::iterator param = params.begin(); param != params.end(); ++param) { dict["param"] = *param; for (size_t step = 0; step < nsteps; ++step) { dict["step"] = toStr(step); for (size_t level = 0; level < nlevels; ++level) { dict["level"] = toStr(level); rendezvous.hashOrder(dict, ordered_nodes); EXPECT(ordered_nodes.size() == 4); counts2[ordered_nodes[0]]++; } } } // Test that we have << roughly >> similar counts on all the nodes. (Very stochastic). for (auto kv : counts2) { EXPECT(2430 < kv.second && 2551 > kv.second); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/linalg/0000775000175000017500000000000015253173651015417 5ustar alastairalastaireckit-2.3.0/tests/linalg/test_la_matrix.cc0000664000175000017500000000172215253173651020747 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "util.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test wrapping of const data") { std::vector array{1., -2., -4., 2.}; const auto* data = array.data(); linalg::Matrix A{data, 2, 2}; linalg::Vector y{data, 2}; // Matrix access is column-major!!! EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == -2.); EXPECT(A(0, 1) == -4.); EXPECT(A(1, 1) == 2.); EXPECT(y[0] == 1.); EXPECT(y[1] == -2.); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { eckit::Main::initialise(argc, argv); return eckit::testing::run_tests(argc, argv, false); } eckit-2.3.0/tests/linalg/test_la_sparse.cc0000664000175000017500000001774615253173651020755 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/config/Resource.h" #include "util.h" #include "eckit/linalg/allocator/NonOwningAllocator.h" #include "eckit/linalg/allocator/StandardContainerAllocator.h" using namespace eckit::linalg; //---------------------------------------------------------------------------------------------------------------------- namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- struct Fixture { const Vector x; const SparseMatrix A; const Vector y; }; /// Test sparse matrix interface CASE("move constructor") { SparseMatrix a{S(3, 3, 4, 0, 0, 2., 0, 2, -3., 1, 1, 2., 2, 2, 2.)}; EXPECT(!a.empty()); SparseMatrix b{std::move(a)}; EXPECT(!b.empty()); } CASE("eckit la sparse") { // "square" fixture // A = 2 . -3 // . 2 . // . . 2 // x = 1 2 3 // y = 1 2 3 Fixture F{V(3, 1., 2., 3.), S(3, 3, 4, 0, 0, 2., 0, 2, -3., 1, 1, 2., 2, 2, 2.), V(3, 1., 2., 3.)}; // "non-square" fixture // A = 1 . 2 // 3 4 . // x = 1 2 // y = 1 2 3 Fixture G{V(2, 1., 2.), S(2, 3, 4, 0, 0, 1., 0, 2, 2., 1, 0, 3., 1, 1, 4.), V(3, 1., 2., 3.)}; SECTION("set from triplets") { // A = 2 0 -3 // 0 2 0 // 0 0 2 EXPECT(F.A.nonZeros() == 4); Index outer[4] = {0, 2, 3, 4}; Index inner[4] = {0, 2, 1, 2}; Scalar data[4] = {2., -3., 2., 2.}; EXPECT(equal_sparse_matrix(F.A, outer, inner, data)); } SECTION("set from triplets with empty rows") { Index outer[7] = {0, 0, 1, 1, 2, 2, 2}; Index inner[2] = {0, 3}; Scalar data[2] = {1., 2.}; EXPECT(equal_sparse_matrix(S(6, 6, 2, 1, 0, 1., 3, 3, 2.), outer, inner, data)); } SECTION("set from triplets with rows in wrong order") { // (not triggering right now since triplets are expected to be sorted) // EXPECT_THROWS_AS( S(2, 2, 2, 1, 1, 1., 0, 0, 1.), AssertionFailed ); } SECTION("copy constructor") { SparseMatrix B(F.A); EXPECT(B.nonZeros() == 4); Index outer[4] = {0, 2, 3, 4}; Index inner[4] = {0, 2, 1, 2}; Scalar data[4] = {2., -3., 2., 2.}; EXPECT(equal_sparse_matrix(B, outer, inner, data)); } SECTION("prune") { SparseMatrix A(S(3, 3, 5, 0, 0, 0., 0, 2, 1., 1, 0, 0., 1, 1, 2., 2, 2, 0.)); A.prune(); EXPECT(A.nonZeros() == 2); Index outer[4] = {0, 1, 2, 2}; Index inner[2] = {2, 1}; Scalar data[2] = {1., 2.}; EXPECT(equal_sparse_matrix(A, outer, inner, data)); } SECTION("row reduction") { SparseMatrix A(S(4, 3, 6, 0, 0, 2., 0, 2, 1., 1, 0, 7., 1, 1, 2., 2, 2, 1., 3, 1, 3.)); // A // 2 . 1 // 7 2 . // . . 1 // . 3 . std::vector p{1, 0}; SparseMatrix B = A.rowReduction(p); // B // 7 2 . // 2 . 1 EXPECT(B.rows() == p.size()); EXPECT(B.nonZeros() == 4); B.dump(Log::info()); Index outer[3] = {0, 2, 4}; Index inner[4] = {0, 1, 0, 2}; Scalar data[4] = {7., 2., 2., 1.}; EXPECT(equal_sparse_matrix(B, outer, inner, data)); } SECTION("iterator") { SparseMatrix A(S(3, 3, 5, 0, 0, 0., 1, 0, 0., 1, 1, 0., 1, 2, 1., 2, 2, 2.)); A.prune(); EXPECT(A.nonZeros() == 2); // data [ 1 2 ] // outer [ 0 0 1 2 ] // inner [ 2 2 ] Scalar data[2] = {1., 2.}; Index outer[4] = {0, 0, 1, 2}; Index inner[2] = {2, 2}; EXPECT(equal_sparse_matrix(A, outer, inner, data)); SparseMatrix::const_iterator it = A.begin(); // check entry #1 EXPECT(it.row() == 1); EXPECT(it.col() == 2); EXPECT(*it == 1.); // check entry #2 ++it; EXPECT(it.row() == 2); EXPECT(it.col() == 2); EXPECT(*it == 2.); // go past the end EXPECT(it != A.end()); ++it; EXPECT(it == A.end()); EXPECT(!it); // go back and re-check entry #1 // (row 0 is empty, should relocate to row 1) it = A.begin(); EXPECT(it); EXPECT(it.row() == 1); EXPECT(it.col() == 2); EXPECT(*it == 1.); // go way past the end it = A.begin(42); EXPECT(!it); } SECTION("transpose square") { Index outer[4] = {0, 1, 2, 4}; Index inner[4] = {0, 1, 0, 2}; Scalar data[4] = {2., 2., -3., 2.}; SparseMatrix B(F.A); EXPECT(equal_sparse_matrix(B.transpose(), outer, inner, data)); } SECTION("transpose non-square") { Index outer[4] = {0, 2, 3, 4}; Index inner[4] = {0, 1, 1, 0}; Scalar data[4] = {1., 3., 4., 2.}; SparseMatrix B(G.A); EXPECT(equal_sparse_matrix(B.transpose(), outer, inner, data)); } SECTION("non-owning allocator") { SparseMatrix FA( new allocator::NonOwningAllocator(F.A.rows(), F.A.cols(), F.A.nonZeros(), const_cast(F.A.outer()), const_cast(F.A.inner()), const_cast(F.A.data()))); EXPECT(F.A.outerIndex() == FA.outerIndex()); EXPECT(F.A.inner() == FA.inner()); EXPECT(F.A.data() == FA.data()); SparseMatrix GA( new allocator::NonOwningAllocator(G.A.rows(), G.A.cols(), G.A.nonZeros(), const_cast(G.A.outer()), const_cast(G.A.inner()), const_cast(G.A.data()))); EXPECT(G.A.outerIndex() == GA.outerIndex()); EXPECT(G.A.inner() == GA.inner()); EXPECT(G.A.data() == GA.data()); } SECTION("containers allocator") { SparseMatrix FA(new allocator::StandardContainerAllocator(3, 3, {{{0, 2.}, {2, -3.}}, {{1, 2.}}, {{2, 2.}}})); EXPECT(equal_sparse_matrix(FA, F.A.outer(), F.A.inner(), F.A.data())); SparseMatrix GA(new allocator::StandardContainerAllocator(2, 3, {{{0, 1.}, {2, 2.}}, {{0, 3.}, {1, 4.}}})); EXPECT(equal_sparse_matrix(GA, GA.outer(), G.A.inner(), G.A.data())); } } //---------------------------------------------------------------------------------------------------------------------- CASE("creation with unassigned triplets ( ECKIT-361 )") { Size N{10}; Size M{8}; Size max_stencil_size = 4; SECTION("only zero triplets, expects throw") { std::vector triplets(N * max_stencil_size); EXPECT_THROWS(SparseMatrix matrix(N, M, triplets)); } SECTION("mixed zero / non-zero triplets") { auto compute_row_triplets = [&](Size row) { std::vector row_triplets(3); for (Size i = 0; i < 3; ++i) { row_triplets[i] = Triplet(row, i, 1. / 3.); } return row_triplets; }; auto skip_point = [](Size row) { if (row == 5) { return true; } return false; }; std::vector triplets(N * max_stencil_size); Size nonzeros{0}; for (Size i = 0; i < N; ++i) { if (!skip_point(i)) { auto row = compute_row_triplets(i); for (Size j = 0; j < row.size(); ++j) { triplets[i * max_stencil_size + j] = row[j]; ++nonzeros; } } } SparseMatrix matrix(N, M, triplets); EXPECT(matrix.rows() == N); EXPECT(matrix.cols() == M); EXPECT(matrix.nonZeros() == nonzeros); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { eckit::Main::initialise(argc, argv); return eckit::testing::run_tests(argc, argv, false); } eckit-2.3.0/tests/linalg/test_la_streaming.cc0000664000175000017500000000473115253173651021437 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/filesystem/PathName.h" #include "eckit/io/AutoCloser.h" #include "eckit/serialisation/FileStream.h" #include "util.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- template void test(const T& v, const T& r) { EXPECT(v.rows() == r.rows()); EXPECT(v.cols() == r.cols()); EXPECT(v.size() == r.size()); EXPECT(testing::make_view(v.begin(), v.end()) == testing::make_view(r.begin(), r.end())); } void test(const linalg::SparseMatrix& v, const linalg::SparseMatrix& r) { EXPECT(v.rows() == r.rows()); EXPECT(v.cols() == r.cols()); EXPECT(v.nonZeros() == r.nonZeros()); const auto nnz = v.nonZeros(); EXPECT(testing::make_view(v.outer(), v.outer() + v.rows() + 1) == testing::make_view(r.outer(), r.outer() + r.rows() + 1)); EXPECT(testing::make_view(v.inner(), v.inner() + nnz) == testing::make_view(r.inner(), r.inner() + nnz)); EXPECT(testing::make_view(v.data(), v.data() + nnz) == testing::make_view(r.data(), r.data() + nnz)); } template void stream_test(const T& t) { PathName filename = PathName::unique("data"); { FileStream sout(filename, "w"); auto c = closer(sout); sout << t; } { FileStream sin(filename, "r"); auto c = closer(sin); T out(sin); test(t, out); } if (filename.exists()) { filename.unlink(); } } //---------------------------------------------------------------------------------------------------------------------- /// Test linear algebra interface CASE("test_stream_vector") { stream_test(V(5, 1., 2., 3., 4., 5.)); } CASE("test_stream_matrix") { stream_test(M(3, 3, 1., 2., 3., 4., 5., 6., 7., 8., 9.)); } CASE("test_stream_sparsematrix") { std::vector triplets; triplets.emplace_back(0, 0, 2.); triplets.emplace_back(0, 2, -3.); triplets.emplace_back(1, 1, 2.); triplets.emplace_back(2, 2, 2.); eckit::linalg::SparseMatrix smat(3, 3, triplets); stream_test(smat); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/linalg/CMakeLists.txt0000664000175000017500000001572615253173651020172 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_add_test( TARGET eckit_test_linalg_factory ARGS --log_level=message SOURCES test_la_factory.cc LIBS eckit_linalg ) ecbuild_add_test( TARGET eckit_test_linalg_dense_backend ARGS --log_level=message SOURCES test_la_linalg_dense.cc util.h LIBS eckit_linalg ) ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend ARGS --log_level=message SOURCES test_la_linalg_sparse.cc util.h LIBS eckit_linalg ) ecbuild_add_test( TARGET eckit_test_linalg_matrix ARGS --log_level=message SOURCES test_la_matrix.cc LIBS eckit_linalg ) ecbuild_add_test( TARGET eckit_test_linalg_tensor ARGS --log_level=message SOURCES test_la_tensor.cc util.h LIBS eckit_linalg ) # LinearAlgebra ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend ARGS --log_level=message SOURCES test_la_linalg.cc util.h LIBS eckit_linalg ) ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend_generic COMMAND eckit_test_linalg_mixed_backend ARGS --log_level=message -linearAlgebraBackend generic ) ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend_cuda COMMAND eckit_test_linalg_mixed_backend CONDITION eckit_HAVE_CUDA ARGS --log_level=message -linearAlgebraBackend cuda --catch_system_errors=no ) ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend_eigen COMMAND eckit_test_linalg_mixed_backend CONDITION eckit_HAVE_EIGEN ARGS --log_level=message -linearAlgebraBackend eigen ) ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend_lapack COMMAND eckit_test_linalg_mixed_backend CONDITION eckit_HAVE_LAPACK ARGS --log_level=message -linearAlgebraBackend lapack ) ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend_mkl COMMAND eckit_test_linalg_mixed_backend CONDITION eckit_HAVE_MKL ARGS --log_level=message -linearAlgebraBackend mkl ) ecbuild_add_test( TARGET eckit_test_linalg_mixed_backend_openmp COMMAND eckit_test_linalg_mixed_backend CONDITION eckit_HAVE_OMP ARGS --log_level=message -linearAlgebraBackend openmp ) # LinearAlgebraDense ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_generic COMMAND eckit_test_linalg_dense_backend ARGS --log_level=message -linearAlgebraDenseBackend generic ) # This test seems to have a system call exit with 1 even though tests pass. # Ignore system errors, see also http://stackoverflow.com/a/20360334/396967 ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_cuda COMMAND eckit_test_linalg_dense_backend CONDITION eckit_HAVE_CUDA ARGS --log_level=message -linearAlgebraDenseBackend cuda --catch_system_errors=no ) ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_eigen COMMAND eckit_test_linalg_dense_backend CONDITION eckit_HAVE_EIGEN ARGS --log_level=message -linearAlgebraDenseBackend eigen ) ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_lapack COMMAND eckit_test_linalg_dense_backend CONDITION eckit_HAVE_LAPACK ARGS --log_level=message -linearAlgebraDenseBackend lapack ) ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_mkl COMMAND eckit_test_linalg_dense_backend CONDITION eckit_HAVE_MKL ARGS --log_level=message -linearAlgebraDenseBackend mkl ) ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_torch COMMAND eckit_test_linalg_dense_backend CONDITION eckit_HAVE_TORCH ARGS --log_level=message -linearAlgebraDenseBackend torch ) ecbuild_add_test( TARGET eckit_test_linalg_dense_backend_openmp COMMAND eckit_test_linalg_dense_backend CONDITION eckit_HAVE_OMP ARGS --log_level=message -linearAlgebraDenseBackend openmp ) # LinearAlgebraSparse ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_generic COMMAND eckit_test_linalg_sparse_backend ARGS --log_level=message -linearAlgebraSparseBackend generic ) # This test seems to have a system call exit with 1 even though tests pass. # Ignore system errors, see also http://stackoverflow.com/a/20360334/396967 ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_cuda COMMAND eckit_test_linalg_sparse_backend CONDITION eckit_HAVE_CUDA ARGS --log_level=message -linearAlgebraSparseBackend cuda --catch_system_errors=no ) ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_hip COMMAND eckit_test_linalg_sparse_backend CONDITION eckit_HAVE_HIP ARGS --log_level=message -linearAlgebraSparseBackend hip ) ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_eigen COMMAND eckit_test_linalg_sparse_backend CONDITION eckit_HAVE_EIGEN ARGS --log_level=message -linearAlgebraSparseBackend eigen ) ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_mkl COMMAND eckit_test_linalg_sparse_backend CONDITION eckit_HAVE_MKL ARGS --log_level=message -linearAlgebraSparseBackend mkl ) ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_openmp COMMAND eckit_test_linalg_sparse_backend CONDITION eckit_HAVE_OMP ARGS --log_level=message -linearAlgebraSparseBackend openmp ) ecbuild_add_test( TARGET eckit_test_linalg_sparse_backend_torch COMMAND eckit_test_linalg_sparse_backend CONDITION eckit_HAVE_TORCH ARGS --log_level=message -linearAlgebraSparseBackend torch ) # ecbuild_add_test( TARGET eckit_test_linalg_sparse ARGS --log_level=message SOURCES test_la_sparse.cc util.h LIBS eckit_linalg ) ecbuild_add_test( TARGET eckit_test_linalg_streaming ARGS --log_level=message SOURCES test_la_streaming.cc util.h LIBS eckit_linalg ) eckit-2.3.0/tests/linalg/test_la_tensor.cc0000664000175000017500000004056015253173651020760 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/config/Resource.h" #include "eckit/io/AutoCloser.h" #include "eckit/serialisation/FileStream.h" #include "util.h" using eckit::linalg::TensorDouble; using eckit::linalg::TensorFloat; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("TensorDouble [2, 2]") { TensorDouble A = TD({2, 2}, 1., -2., -4., 3.); // column-major order EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == -2.); EXPECT(A(0, 1) == -4.); EXPECT(A(1, 1) == 3.); } CASE("TensorDouble [2, 3, 4]") { TensorDouble A = TD({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); } CASE("TensorDouble [2, 3, 4, 5]") { std::vector shape{2, 2, 3, 5}; std::vector v(TensorDouble::flatSize(shape)); for (size_t i = 0; i < v.size(); ++i) { v[i] = double(i + 1); } TensorDouble A(v.data(), shape); // column-major order EXPECT(A(0, 0, 0, 0) == 1.); EXPECT(A(1, 0, 0, 0) == 2.); EXPECT(A(0, 1, 0, 0) == 3.); EXPECT(A(1, 1, 0, 0) == 4.); EXPECT(A(1, 1, 2, 0) == 12.); EXPECT(A(0, 0, 2, 4) == 57.); EXPECT(A(1, 0, 2, 4) == 58.); EXPECT(A(0, 1, 2, 4) == 59.); EXPECT(A(1, 1, 2, 4) == 60.); } CASE("TensorDouble serialization") { TensorDouble A = TD({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); PathName path("tensorA"); { FileStream s(path, "w"); auto c = closer(s); s << A; } FileStream s(path, "r"); auto c = closer(s); TensorDouble B(s); // column-major order EXPECT(B(0, 0, 0) == 1.); EXPECT(B(1, 0, 0) == -2.); EXPECT(B(0, 1, 0) == 3.); EXPECT(B(1, 1, 0) == -4.); EXPECT(B(1, 1, 2) == -12.); path.unlink(); } CASE("TensorDouble zero") { TensorDouble A = TD({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); A.zero(); EXPECT(A(1, 0, 0) == 0.); EXPECT(A(0, 1, 0) == 0.); EXPECT(A(1, 1, 0) == 0.); EXPECT(A(1, 1, 2) == 0.); } CASE("Fill TensorDouble with scalar") { TensorDouble A = TD({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); A.fill(-7.42); EXPECT(A(1, 0, 0) == -7.42); EXPECT(A(0, 1, 0) == -7.42); EXPECT(A(1, 1, 0) == -7.42); EXPECT(A(0, 1, 2) == -7.42); EXPECT(A(1, 1, 2) == -7.42); } CASE("Fill TensorDouble with scalar") { TensorDouble A = TD({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); double v = -7.42; A.fill(v); EXPECT(A(1, 0, 0) == v); EXPECT(A(0, 1, 0) == v); EXPECT(A(1, 1, 0) == v); EXPECT(A(0, 1, 2) == v); EXPECT(A(1, 1, 2) == v); } CASE("TensorDouble wrapping const data") { std::vector array{1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.}; auto* data = array.data(); TensorDouble A{data, {2, 2, 3}}; // // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == 2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == 4.); EXPECT(A(1, 1, 1) == 8.); EXPECT(A(0, 0, 2) == 9.); EXPECT(A(1, 1, 2) == 12.); } //---------------------------------------------------------------------------------------------------------------------- CASE("TensorFloat [2, 2]") { TensorFloat A = TF({2, 2}, 1., -2., -4., 3.); // column-major order EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == -2.); EXPECT(A(0, 1) == -4.); EXPECT(A(1, 1) == 3.); } CASE("TensorFloat [2, 3, 4]") { TensorFloat A = TF({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); } CASE("TensorFloat [2, 3, 4, 5]") { std::vector shape{2, 2, 3, 5}; std::vector v(TensorFloat::flatSize(shape)); for (size_t i = 0; i < v.size(); ++i) { v[i] = float(i + 1); } TensorFloat A(v.data(), shape); // column-major order EXPECT(A(0, 0, 0, 0) == 1.); EXPECT(A(1, 0, 0, 0) == 2.); EXPECT(A(0, 1, 0, 0) == 3.); EXPECT(A(1, 1, 0, 0) == 4.); EXPECT(A(1, 1, 2, 0) == 12.); EXPECT(A(0, 0, 2, 4) == 57.); EXPECT(A(1, 0, 2, 4) == 58.); EXPECT(A(0, 1, 2, 4) == 59.); EXPECT(A(1, 1, 2, 4) == 60.); } CASE("TensorFloat serialization") { TensorFloat A = TF({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); PathName path("tensorA"); { FileStream s(path, "w"); auto c = closer(s); s << A; } FileStream s(path, "r"); auto c = closer(s); TensorFloat B(s); // column-major order EXPECT(B(0, 0, 0) == 1.); EXPECT(B(1, 0, 0) == -2.); EXPECT(B(0, 1, 0) == 3.); EXPECT(B(1, 1, 0) == -4.); EXPECT(B(1, 1, 2) == -12.); path.unlink(); } CASE("TensorFloat zero") { TensorFloat A = TF({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); A.zero(); EXPECT(A(1, 0, 0) == 0.); EXPECT(A(0, 1, 0) == 0.); EXPECT(A(1, 1, 0) == 0.); EXPECT(A(1, 1, 2) == 0.); } CASE("Fill TensorFloat with scalar") { TensorFloat A = TF({2, 2, 3}, 1., -2., 3., -4., 5., -6., 7., -8., 9., -10., 11., -12.); // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == -2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == -4.); EXPECT(A(1, 1, 2) == -12.); float v = -7.42; A.fill(v); EXPECT(A(1, 0, 0) == v); EXPECT(A(0, 1, 0) == v); EXPECT(A(1, 1, 0) == v); EXPECT(A(0, 1, 2) == v); EXPECT(A(1, 1, 2) == v); } CASE("TensorFloat wrapping const data") { std::vector array{1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.}; auto* data = array.data(); TensorFloat A{data, {2, 2, 3}}; // // column-major order EXPECT(A(0, 0, 0) == 1.); EXPECT(A(1, 0, 0) == 2.); EXPECT(A(0, 1, 0) == 3.); EXPECT(A(1, 1, 0) == 4.); EXPECT(A(1, 1, 1) == 8.); EXPECT(A(0, 0, 2) == 9.); EXPECT(A(1, 1, 2) == 12.); } CASE("TensorFloat [2, 3] col-major to row-major layout") { std::vector array{1., 2., 3., 4., 5., 6.}; TensorFloat A{array.data(), {2, 3}}; TensorFloat A_rowMajor, A_colMajor; // column-major order EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == 2.); EXPECT(A(0, 1) == 3.); EXPECT(A(1, 1) == 4.); EXPECT(A(0, 2) == 5.); EXPECT(A(1, 2) == 6.); // ** to row-major layout ** A_rowMajor = A.transformColMajorToRowMajor(); // A internal order should not change! for (linalg::Size i = 0; i < A.size(); i++) { EXPECT(*(A.data() + i) == array[i]); } // and no change in the // retrieved values EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == 2.); EXPECT(A(0, 1) == 3.); EXPECT(A(1, 1) == 4.); EXPECT(A(0, 2) == 5.); EXPECT(A(1, 2) == 6.); // internal order is now changed! std::vector expected_rowmajor_values{1., 3., 5., 2., 4., 6.}; for (linalg::Size i = 0; i < A_rowMajor.size(); i++) { EXPECT(*(A_rowMajor.data() + i) == expected_rowmajor_values[i]); } // ** to col-major layout ** A_colMajor = A_rowMajor.transformRowMajorToColMajor(); // back to the original order EXPECT(A_colMajor(0, 0) == 1.); EXPECT(A_colMajor(1, 0) == 2.); EXPECT(A_colMajor(0, 1) == 3.); EXPECT(A_colMajor(1, 1) == 4.); EXPECT(A_colMajor(0, 2) == 5.); EXPECT(A_colMajor(1, 2) == 6.); } CASE("TensorFloat [2, 3] col-major->row-major->col-major layout") { std::vector array{1., 2., 3., 4., 5., 6.}; TensorFloat A{array.data(), {2, 3}}; TensorFloat A_rowMajor, A_colMajor; // A internal order for (linalg::Size i = 0; i < A.size(); i++) { EXPECT(*(A.data() + i) == array[i]); } // column-major order EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == 2.); EXPECT(A(0, 1) == 3.); EXPECT(A(1, 1) == 4.); EXPECT(A(0, 2) == 5.); EXPECT(A(1, 2) == 6.); // ** to row-major layout ** A_rowMajor = A.transformColMajorToRowMajor(); // internal order is now changed! std::vector expected_rowmajor_values{1., 3., 5., 2., 4., 6.}; for (linalg::Size i = 0; i < A_rowMajor.size(); i++) { EXPECT(*(A_rowMajor.data() + i) == expected_rowmajor_values[i]); } // indexes still OK EXPECT(A(0, 0) == 1.); EXPECT(A(1, 0) == 2.); EXPECT(A(0, 1) == 3.); EXPECT(A(1, 1) == 4.); EXPECT(A(0, 2) == 5.); EXPECT(A(1, 2) == 6.); // ** back to col-major layout ** A_colMajor = A_rowMajor.transformRowMajorToColMajor(); // check that the internal order is back to initial order for (linalg::Size i = 0; i < A_colMajor.size(); i++) { EXPECT(*(A_colMajor.data() + i) == array[i]); } // indexes still OK EXPECT(A_colMajor(0, 0) == 1.); EXPECT(A_colMajor(1, 0) == 2.); EXPECT(A_colMajor(0, 1) == 3.); EXPECT(A_colMajor(1, 1) == 4.); EXPECT(A_colMajor(0, 2) == 5.); EXPECT(A_colMajor(1, 2) == 6.); } CASE("TensorFloat [2, 3, 4] col-major->row-major->col-major layout") { std::vector array(24); for (int i = 0; i < 24; i++) { array[i] = i; } TensorFloat A{array.data(), {2, 3, 4}}; TensorFloat A_rowMajor, A_colMajor; // A internal order for (linalg::Size i = 0; i < A.size(); i++) { EXPECT(*(A.data() + i) == array[i]); } // indexes EXPECT(A(0, 0, 0) == 0.); EXPECT(A(1, 0, 0) == 1.); EXPECT(A(0, 1, 0) == 2.); EXPECT(A(1, 1, 0) == 3.); EXPECT(A(0, 2, 0) == 4.); EXPECT(A(1, 2, 0) == 5.); EXPECT(A(0, 0, 1) == 6.); EXPECT(A(1, 0, 1) == 7.); EXPECT(A(0, 1, 1) == 8.); EXPECT(A(1, 1, 1) == 9.); EXPECT(A(0, 2, 1) == 10.); EXPECT(A(1, 2, 1) == 11.); EXPECT(A(0, 0, 2) == 12.); EXPECT(A(1, 0, 2) == 13.); EXPECT(A(0, 1, 2) == 14.); EXPECT(A(1, 1, 2) == 15.); EXPECT(A(0, 2, 2) == 16.); EXPECT(A(1, 2, 2) == 17.); EXPECT(A(0, 0, 3) == 18.); EXPECT(A(1, 0, 3) == 19.); EXPECT(A(0, 1, 3) == 20.); EXPECT(A(1, 1, 3) == 21.); EXPECT(A(0, 2, 3) == 22.); EXPECT(A(1, 2, 3) == 23.); // ** to row-major layout ** A_rowMajor = A.transformColMajorToRowMajor(); // internal order is now changed! std::vector expected_rowmajor_values{0, 6, 12, 18, 2, 8, 14, 20, 4, 10, 16, 22, 1, 7, 13, 19, 3, 9, 15, 21, 5, 11, 17, 23}; for (linalg::Size i = 0; i < A_rowMajor.size(); i++) { EXPECT(*(A_rowMajor.data() + i) == expected_rowmajor_values[i]); } // indexes still OK EXPECT(A(0, 0, 0) == 0.); EXPECT(A(1, 0, 0) == 1.); EXPECT(A(0, 1, 0) == 2.); EXPECT(A(1, 1, 0) == 3.); EXPECT(A(0, 2, 0) == 4.); EXPECT(A(1, 2, 0) == 5.); EXPECT(A(0, 0, 1) == 6.); EXPECT(A(1, 0, 1) == 7.); EXPECT(A(0, 1, 1) == 8.); EXPECT(A(1, 1, 1) == 9.); EXPECT(A(0, 2, 1) == 10.); EXPECT(A(1, 2, 1) == 11.); EXPECT(A(0, 0, 2) == 12.); EXPECT(A(1, 0, 2) == 13.); EXPECT(A(0, 1, 2) == 14.); EXPECT(A(1, 1, 2) == 15.); EXPECT(A(0, 2, 2) == 16.); EXPECT(A(1, 2, 2) == 17.); EXPECT(A(0, 0, 3) == 18.); EXPECT(A(1, 0, 3) == 19.); EXPECT(A(0, 1, 3) == 20.); EXPECT(A(1, 1, 3) == 21.); EXPECT(A(0, 2, 3) == 22.); EXPECT(A(1, 2, 3) == 23.); // ** back to col-major layout ** A_colMajor = A_rowMajor.transformRowMajorToColMajor(); // check that the internal order is back to initial order for (linalg::Size i = 0; i < A_colMajor.size(); i++) { EXPECT(*(A_colMajor.data() + i) == array[i]); } } CASE("TensorFloat [6, 3, 2, 5] col-major->row-major->col-major layout") { std::vector array(6 * 3 * 2 * 5); for (size_t i = 0; i < array.size(); i++) { array[i] = i; } // tensor A (col-major-layout by default) TensorFloat A{array.data(), {6, 3, 2, 5}}; TensorFloat A_rowMajor, A_colMajor; // check A internal order for (linalg::Size i = 0; i < A.size(); i++) { EXPECT(*(A.data() + i) == array[i]); } // ** to row-major layout ** A_rowMajor = A.transformColMajorToRowMajor(); // internal order is now changed! std::vector expected_rowmajor_values{ 0, 36, 72, 108, 144, 18, 54, 90, 126, 162, 6, 42, 78, 114, 150, 24, 60, 96, 132, 168, 12, 48, 84, 120, 156, 30, 66, 102, 138, 174, 1, 37, 73, 109, 145, 19, 55, 91, 127, 163, 7, 43, 79, 115, 151, 25, 61, 97, 133, 169, 13, 49, 85, 121, 157, 31, 67, 103, 139, 175, 2, 38, 74, 110, 146, 20, 56, 92, 128, 164, 8, 44, 80, 116, 152, 26, 62, 98, 134, 170, 14, 50, 86, 122, 158, 32, 68, 104, 140, 176, 3, 39, 75, 111, 147, 21, 57, 93, 129, 165, 9, 45, 81, 117, 153, 27, 63, 99, 135, 171, 15, 51, 87, 123, 159, 33, 69, 105, 141, 177, 4, 40, 76, 112, 148, 22, 58, 94, 130, 166, 10, 46, 82, 118, 154, 28, 64, 100, 136, 172, 16, 52, 88, 124, 160, 34, 70, 106, 142, 178, 5, 41, 77, 113, 149, 23, 59, 95, 131, 167, 11, 47, 83, 119, 155, 29, 65, 101, 137, 173, 17, 53, 89, 125, 161, 35, 71, 107, 143, 179}; for (linalg::Size i = 0; i < A_rowMajor.size(); i++) { EXPECT(*(A_rowMajor.data() + i) == expected_rowmajor_values[i]); } // ** back to col-major layout ** A_colMajor = A_rowMajor.transformRowMajorToColMajor(); // check that the internal order is back to initial order for (linalg::Size i = 0; i < A_colMajor.size(); i++) { EXPECT(*(A_colMajor.data() + i) == array[i]); } } CASE("TensorFloat move constructor") { std::vector data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; std::vector shape{3, 4}; TensorFloat tensor_from(data.data(), shape, TensorFloat::Layout::Right); // force move constructor TensorFloat tensor_to(std::move(tensor_from)); EXPECT(tensor_to.shape()[0] == 3); EXPECT(tensor_to.shape()[1] == 4); EXPECT(tensor_to(0, 0) == 0); EXPECT(tensor_to(0, 1) == 1); EXPECT(tensor_to(0, 2) == 2); EXPECT(tensor_to(0, 3) == 3); EXPECT(tensor_to(0, 4) == 4); EXPECT(tensor_to(0, 5) == 5); EXPECT(tensor_to(0, 6) == 6); EXPECT(tensor_to(0, 7) == 7); EXPECT(tensor_to(0, 8) == 8); EXPECT(tensor_to(0, 9) == 9); EXPECT(tensor_to(0, 10) == 10); EXPECT(tensor_to(0, 11) == 11); EXPECT(tensor_from.data() == nullptr); } CASE("TensorFloat move assignment operator") { std::vector data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; std::vector shape{3, 4}; TensorFloat tensor_from(data.data(), shape, TensorFloat::Layout::Right); // force move assignment TensorFloat tensor_to; tensor_to = std::move(tensor_from); EXPECT(tensor_to.shape()[0] == 3); EXPECT(tensor_to.shape()[1] == 4); EXPECT(tensor_to(0, 0) == 0); EXPECT(tensor_to(0, 1) == 1); EXPECT(tensor_to(0, 2) == 2); EXPECT(tensor_to(0, 3) == 3); EXPECT(tensor_to(0, 4) == 4); EXPECT(tensor_to(0, 5) == 5); EXPECT(tensor_to(0, 6) == 6); EXPECT(tensor_to(0, 7) == 7); EXPECT(tensor_to(0, 8) == 8); EXPECT(tensor_to(0, 9) == 9); EXPECT(tensor_to(0, 10) == 10); EXPECT(tensor_to(0, 11) == 11); EXPECT(tensor_from.data() == nullptr); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { eckit::Main::initialise(argc, argv); return eckit::testing::run_tests(argc, argv, false); } eckit-2.3.0/tests/linalg/test_la_factory.cc0000664000175000017500000000502715253173651021114 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/eckit.h" #include "eckit/linalg/LinearAlgebraDense.h" #include "eckit/linalg/LinearAlgebraSparse.h" #include "eckit/testing/Test.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("list") { using linalg::LinearAlgebraDense; using linalg::LinearAlgebraSparse; auto dense_backends = { "generic", #if eckit_HAVE_CUDA "cuda", #endif #if eckit_HAVE_EIGEN "eigen", #endif #if eckit_HAVE_LAPACK "lapack", #endif #if eckit_HAVE_MKL "mkl", #endif #if eckit_HAVE_OMP "openmp", #endif }; auto sparse_backends = { "generic", #if eckit_HAVE_CUDA "cuda", #endif #if eckit_HAVE_EIGEN "eigen", #endif #if eckit_HAVE_MKL #if eckit_HAVE_MKL "mkl", #endif #endif #if eckit_HAVE_OMP "openmp", #endif }; SECTION("dense backends") { std::ostringstream oss; LinearAlgebraDense::list(oss); Log::info() << "Available backends: " << oss.str() << std::endl; for (std::string name : dense_backends) { Log::info() << "Looking for '" << name << "'" << std::endl; EXPECT(oss.str().find(name) != std::string::npos); Log::info() << "Switching to '" << name << "'" << std::endl; LinearAlgebraDense::backend(name); EXPECT(LinearAlgebraDense::backend().name() == name); } EXPECT_THROWS_AS(LinearAlgebraDense::backend("foo"), BadParameter); } SECTION("sparse backends") { std::ostringstream oss; LinearAlgebraSparse::list(oss); Log::info() << "Available backends: " << oss.str() << std::endl; for (std::string name : sparse_backends) { Log::info() << "Looking for '" << name << "'" << std::endl; EXPECT(oss.str().find(name) != std::string::npos); Log::info() << "Switching to '" << name << "'" << std::endl; LinearAlgebraSparse::backend(name); EXPECT(LinearAlgebraSparse::backend().name() == name); } EXPECT_THROWS_AS(LinearAlgebraSparse::backend("foo"), BadParameter); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/linalg/test_la_linalg_dense.cc0000664000175000017500000000431715253173651022072 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/config/Resource.h" #include "eckit/linalg/LinearAlgebraDense.h" #include "util.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- /// Test dense linear algebra interface CASE("test backend") { using linalg::Matrix; using linalg::Vector; Matrix A = M(2, 2, 1., -2., -4., 2.); const auto& linalg = linalg::LinearAlgebraDense::backend(); Log::info() << linalg << std::endl; SECTION("dot") { auto a = V(3, 1., 2., 4.); EXPECT(linalg.dot(a, a) == 21.); EXPECT_THROWS_AS(linalg.dot(a, Vector(2)), AssertionFailed); } SECTION("gemv") { auto y = V(2, -42., -42.); auto z = V(2, 3., 0.); linalg.gemv(A, V(2, -1., -2.), y); EXPECT(equal_dense_matrix(y, z)); EXPECT_THROWS_AS(linalg.gemv(A, Vector(3), y), AssertionFailed); } SECTION("gemm - 2x2 x 2x2") { auto Y = M(2, 2, -42., -42., -42., -42.); auto Z = M(2, 2, 9., -6., -12., 12.); linalg.gemm(A, A, Y); EXPECT(equal_dense_matrix(Y, Z)); EXPECT_THROWS_AS(linalg.gemm(A, Matrix(1, 2), Y), AssertionFailed); } SECTION("gemm - 2x4 x 4x3") { auto A = M(2, 4, 1., 2., 3., 4., 5., 6., 7., 8.); auto B = M(4, 3, 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20.); auto C = M(2, 3, 0., 0., 0., 0., 0., 0.); auto C_check = M(2, 3, 150., 160., 170., // row=0 366., 392., 418.); // row=1 linalg.gemm(A, B, C); EXPECT(equal_dense_matrix(C, C_check)); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { eckit::Main::initialise(argc, argv); // Set dense linear algebra backend eckit::linalg::LinearAlgebraDense::backend(eckit::Resource("-linearAlgebraDenseBackend", "generic")); return eckit::testing::run_tests(argc, argv, false); } eckit-2.3.0/tests/linalg/util.h0000664000175000017500000000727215253173651016555 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #pragma once #include #include "eckit/linalg/Matrix.h" #include "eckit/linalg/SparseMatrix.h" #include "eckit/linalg/Tensor.h" #include "eckit/linalg/Vector.h" #include "eckit/testing/Test.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- linalg::Vector V(linalg::Size s, ...) { using linalg::Scalar; using linalg::Size; using linalg::Vector; Vector vec(s); va_list args; va_start(args, s); for (Size i = 0; i < s; ++i) { vec[i] = va_arg(args, Scalar); } va_end(args); return vec; } linalg::Matrix M(linalg::Size rows, linalg::Size cols, ...) { using linalg::Matrix; using linalg::Scalar; using linalg::Size; Matrix mat(rows, cols); va_list args; va_start(args, cols); for (Size r = 0; r < rows; ++r) { for (Size c = 0; c < cols; ++c) { mat(r, c) = va_arg(args, Scalar); } } va_end(args); return mat; } linalg::SparseMatrix S(linalg::Size rows, linalg::Size cols, linalg::Size nnz, ...) { using linalg::Scalar; using linalg::Size; using linalg::Triplet; va_list args; va_start(args, nnz); std::vector triplets; for (Size n = 0; n < nnz; ++n) { Size row = Size(va_arg(args, int)); Size col = Size(va_arg(args, int)); Scalar v = va_arg(args, Scalar); triplets.push_back(Triplet(row, col, v)); } va_end(args); linalg::SparseMatrix mat(rows, cols, triplets); // ECKIT_DEBUG_VAR(mat.nonZeros()); // ECKIT_DEBUG_VAR(mat.rows()); // ECKIT_DEBUG_VAR(mat.cols()); return mat; } linalg::TensorDouble TD(std::vector shape, ...) { using linalg::Scalar; using linalg::Size; using linalg::TensorDouble; Size size = linalg::TensorDouble::flatSize(shape); TensorDouble t(shape); va_list args; va_start(args, shape); for (Size i = 0; i < size; ++i) { t[i] = va_arg(args, Scalar); } va_end(args); return t; } linalg::TensorFloat TF(std::vector shape, ...) { using linalg::Scalar; using linalg::Size; using linalg::TensorFloat; Size size = linalg::TensorFloat::flatSize(shape); TensorFloat t(shape); va_list args; va_start(args, shape); for (Size i = 0; i < size; ++i) { t[i] = va_arg(args, Scalar); } va_end(args); return t; } //---------------------------------------------------------------------------------------------------------------------- template bool equal_array(T* v, T* r, size_t s) { return testing::make_view(v, s) == testing::make_view(r, s); } template bool equal_dense_matrix(const T& a, const T& b) { return a.size() == b.size() && testing::make_view(a.data(), a.data() + a.size()) == testing::make_view(b.data(), b.data() + b.size()); } template bool equal_dense_matrix(const T& v, const T& r, size_t s) { return is_approximately_equal(testing::make_view(v.data(), s), testing::make_view(r.data(), s), 0.1); } bool equal_sparse_matrix(const linalg::SparseMatrix& A, const linalg::Index* outer, const linalg::Index* inner, const linalg::Scalar* data) { return equal_array(A.outer(), outer, A.rows() + 1) && equal_array(A.inner(), inner, A.nonZeros()) && equal_array(A.data(), data, A.nonZeros()); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test eckit-2.3.0/tests/linalg/test_la_linalg_sparse.cc0000664000175000017500000001210215253173651022260 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/config/Resource.h" #include "eckit/linalg/LinearAlgebraSparse.h" #include "util.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- /// Test sparse linear algebra interface CASE("test backend") { using linalg::Matrix; using linalg::SparseMatrix; using linalg::Vector; SparseMatrix Q(2, 4, {{0, 0, 2.}, {1, 2, 3.}}); auto A = S(3, 3, 4, 0, 0, 2., 0, 2, -3., 1, 1, 2., 2, 2, 2.); auto x = V(3, 1., 2., 3.); const auto& linalg = linalg::LinearAlgebraSparse::backend(); Log::info() << linalg << std::endl; SECTION("spmv - sparse 2x4 x vector 4 = vector 2") { auto b = V(4, 5., 5., 5., 5.); auto y = V(2, -42., -42.); auto z = V(2, 10., 15.); linalg.spmv(Q, b, y); EXPECT(equal_dense_matrix(y, z)); EXPECT_THROWS_AS(linalg.spmv(Q, Vector(3), y), AssertionFailed); } SECTION("spmv - sparse identity 3x3 x vector 3 = vector 3") { Vector y(3); SparseMatrix B; B.setIdentity(3, 3); linalg.spmv(B, x, y); EXPECT(equal_dense_matrix(y, x)); } SECTION("spmv - sparse identity 6x3 x vector 3 = vector 6") { SparseMatrix C; C.setIdentity(6, 3); Vector y(6); linalg.spmv(C, x, y); EXPECT(equal_dense_matrix(y, x, 3)); EXPECT(equal_array(y.data() + 3, V(3, 0., 0., 0.).data(), 3)); } SECTION("spmv - sparse identity 2x3 x vector 3 = vector 2") { SparseMatrix D; D.setIdentity(2, 3); Vector y(2); linalg.spmv(D, x, y); EXPECT(equal_dense_matrix(y, x, 2)); } SECTION("spmv - sparse 3x3 x vector 3 = vector 3") { Vector y(3); linalg.spmv(A, x, y); EXPECT(equal_dense_matrix(y, V(3, -7., 4., 6.))); Log::info() << "spmv of sparse matrix and vector of non-matching sizes should fail" << std::endl; EXPECT_THROWS_AS(linalg.spmv(A, Vector(2), y), AssertionFailed); } SECTION("spmm - sparse 2x4 x matrix 4x2 = matrix 2x2") { auto B = M(4, 2, 5., 7., 5., 7., 5., 7., 5., 7.); auto Y = M(2, 2, -42., -42., -42., -42.); auto Z = M(2, 2, 10., 14., 15., 21.); linalg.spmm(Q, B, Y); EXPECT(equal_dense_matrix(Y, Z)); EXPECT_THROWS_AS(linalg.spmm(Q, Matrix(2, 2), Y), AssertionFailed); } SECTION("spmm - sparse 3x3 x matrix 3x2 = matrix 3x2") { Matrix C(3, 2); linalg.spmm(A, M(3, 2, 1., 2., 3., 4., 5., 6.), C); EXPECT(equal_dense_matrix(C, M(3, 2, -13., -14., 6., 8., 10., 12.))); Log::info() << "spmm of sparse matrix and matrix of non-matching sizes should fail" << std::endl; EXPECT_THROWS_AS(linalg.spmm(A, Matrix(2, 2), C), AssertionFailed); } SECTION("dsptd - diagonal 3 x sparse 3x3 x diagonal 3 = sparse 3x3") { SparseMatrix B; linalg.dsptd(x, A, x, B); linalg::Index outer[4] = {0, 2, 3, 4}; linalg::Index inner[4] = {0, 2, 1, 2}; linalg::Scalar data[4] = {2., -9., 8., 18.}; EXPECT(equal_sparse_matrix(B, outer, inner, data)); Log::info() << "dsptd with matrices of non-matching sizes should fail" << std::endl; EXPECT_THROWS_AS(linalg.dsptd(x, A, Vector(2), B), AssertionFailed); } SECTION("dsptd - diagonal 2 x sparse 2x3 x diagonal 3 = sparse 2x3") { auto x = V(2, 1., 2.); auto y = V(3, 1., 2., 3.); auto Q = S(2, 3, 4, 0, 0, 1., 0, 2, 2., 1, 0, 3., 1, 1, 4.); SparseMatrix B; linalg.dsptd(x, Q, y, B); // B.save("B.mat"); linalg::Index outer[4] = {0, 2, 4}; linalg::Index inner[4] = {0, 2, 0, 1}; linalg::Scalar data[4] = {1., 6., 6., 16.}; EXPECT(equal_sparse_matrix(B, outer, inner, data)); Log::info() << "dsptd with matrices of non-matching sizes should fail" << std::endl; EXPECT_THROWS_AS(linalg.dsptd(y, Q, x, B), AssertionFailed); } SECTION("dsptd - diagonal 2 x sparse 2x4 x diagonal 4 = sparse 2x4") { auto x = V(2, 1., 2.); auto y = V(4, 3., 4., 5., 6.); SparseMatrix B; linalg.dsptd(x, Q, y, B); linalg::Index outer[4] = {0, 1, 2}; linalg::Index inner[4] = {0, 2}; linalg::Scalar data[4] = {6., 30.}; EXPECT(equal_sparse_matrix(B, outer, inner, data)); Log::info() << "dsptd with matrices of non-matching sizes should fail" << std::endl; EXPECT_THROWS_AS(linalg.dsptd(y, Q, x, B), AssertionFailed); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { eckit::Main::initialise(argc, argv); // Set sparse linear algebra backend eckit::linalg::LinearAlgebraSparse::backend(eckit::Resource("-linearAlgebraSparseBackend", "generic")); return eckit::testing::run_tests(argc, argv, false); } eckit-2.3.0/tests/linalg/test_la_linalg.cc0000664000175000017500000000654715253173651020723 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/config/Resource.h" #include "eckit/linalg/LinearAlgebra.h" #include "util.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- /// Test linear algebra interface CASE("test backend") { using linalg::LinearAlgebra; using linalg::Matrix; using linalg::SparseMatrix; using linalg::Vector; auto A = M(2, 2, 1., -2., -4., 2.); SparseMatrix S(2, 4, {{0, 0, 2.}, {1, 2, 3.}}); Log::info() << "LinearAlgebra[dense=" << linalg::LinearAlgebraDense::backend() << ",sparse=" << linalg::LinearAlgebraSparse::backend() << "]" << std::endl; SECTION("dot") { auto a = V(3, 1., 2., 4.); EXPECT(LinearAlgebra::dot(a, a) == 21.); EXPECT_THROWS_AS(LinearAlgebra::dot(a, Vector(2)), AssertionFailed); } SECTION("gemv") { auto y = V(2, -42., -42.); auto z = V(2, 3., 0.); LinearAlgebra::gemv(A, V(2, -1., -2.), y); EXPECT(equal_dense_matrix(y, z)); EXPECT_THROWS_AS(LinearAlgebra::gemv(A, Vector(3), y), AssertionFailed); } SECTION("gemm") { auto Y = M(2, 2, -42., -42., -42., -42.); auto Z = M(2, 2, 9., -6., -12., 12.); LinearAlgebra::gemm(A, A, Y); EXPECT(equal_dense_matrix(Y, Z)); EXPECT_THROWS_AS(LinearAlgebra::gemm(A, Matrix(1, 2), Y), AssertionFailed); } SECTION("spmv") { auto b = V(4, 5., 5., 5., 5.); auto y = V(2, -42., -42.); auto z = V(2, 10., 15.); LinearAlgebra::spmv(S, b, y); EXPECT(equal_dense_matrix(y, z)); EXPECT_THROWS_AS(LinearAlgebra::spmv(S, Vector(3), y), AssertionFailed); } SECTION("spmm") { auto B = M(4, 2, 5., 7., 5., 7., 5., 7., 5., 7.); auto Y = M(2, 2, -42., -42., -42., -42.); auto Z = M(2, 2, 10., 14., 15., 21.); LinearAlgebra::spmm(S, B, Y); EXPECT(equal_dense_matrix(Y, Z)); EXPECT_THROWS_AS(LinearAlgebra::spmm(S, Matrix(2, 2), Y), AssertionFailed); } SECTION("dsptd") { auto x = V(2, 1., 2.); auto y = V(4, 3., 4., 5., 6.); SparseMatrix R(2, 4, {{0, 0, 6.}, {1, 2, 30.}}); SparseMatrix P; LinearAlgebra::dsptd(x, S, y, P); EXPECT(P.rows() == R.rows()); EXPECT(P.cols() == R.cols()); EXPECT(equal_sparse_matrix(P, R.outer(), R.inner(), R.data())); EXPECT_THROWS_AS(LinearAlgebra::dsptd(y, S, x, P), AssertionFailed); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { using eckit::linalg::LinearAlgebra; eckit::Main::initialise(argc, argv); // Set dense/sparse linear algebra backends eckit::Resource backend("-linearAlgebraBackend", "generic"); auto setDense = LinearAlgebra::hasDenseBackend(backend); auto setSparse = LinearAlgebra::hasSparseBackend(backend); ASSERT(setDense || setSparse); if (setDense) { LinearAlgebra::denseBackend(backend); } if (setSparse) { LinearAlgebra::sparseBackend(backend); } return eckit::testing::run_tests(argc, argv, false); } eckit-2.3.0/tests/option/0000775000175000017500000000000015253173651015461 5ustar alastairalastaireckit-2.3.0/tests/option/eckit_test_option_factory.cc0000664000175000017500000000523215253173651023247 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/option/FactoryOption.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- /// Build some generic factories to demonstrate functionality. /// n.b. This does not worry about thread safety in the factory for the purpose of these unit tests. /// Only one thread... namespace { /// A general base type, for objects to be built from. class ObjectBase { public: ObjectBase() {} }; /// A general object factory. class ObjectFactory { std::string name_; virtual ObjectBase* make() const = 0; static std::map& factory() { static std::map m; return m; } protected: ObjectFactory(const std::string& nm) : name_(nm) { factory()[nm] = this; } virtual ~ObjectFactory() { factory().erase(name_); } public: static void list(std::ostream& out) { const char* sep = ""; for (std::map::const_iterator j = factory().begin(); j != factory().end(); ++j) { out << sep << (*j).first; sep = ", "; } } /* // unused static ObjectBase* build(const std::string& nm) { return factory()[nm]->make(); } */ }; /// Templated class to self-register, and build, objects template class ObjectBuilder : public ObjectFactory { virtual ObjectBase* make() const { return new T; } public: ObjectBuilder(const std::string& name) : ObjectFactory(name) {} }; /// Register the types class Object1 : public ObjectBase {}; class Object2 : public ObjectBase {}; ObjectBuilder factory1("obj1"); ObjectBuilder factory2("obj2"); } // anonymous namespace //---------------------------------------------------------------------------------------------------------------------- using namespace eckit::option; CASE("test_eckit_option_factory_list") { FactoryOption opt("arg1", "description"); // Extract the output std::stringstream ss; ss << opt; std::string opt_str(ss.str()); std::string cmp = " --arg1=name (description)\n Values are: obj1, obj2"; EXPECT(opt_str == cmp); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/option/CMakeLists.txt0000664000175000017500000000076415253173651020230 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 foreach( TESTCASE RANGE 1 40 ) ecbuild_add_test( TARGET eckit_test_option_cmdargs_${TESTCASE} SOURCES eckit_test_option_cmdargs.cc LIBS eckit_option DEFINITIONS TESTCASE=${TESTCASE} ) endforeach() ecbuild_add_test( TARGET eckit_test_option_factory SOURCES eckit_test_option_factory.cc LIBS eckit_option ) eckit-2.3.0/tests/option/eckit_test_option_cmdargs.cc0000664000175000017500000010300415253173651023214 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/option/CmdArgs.h" #include "eckit/option/MultiValueOption.h" #include "eckit/option/Separator.h" #include "eckit/option/SimpleOption.h" #include "eckit/option/VectorOption.h" #include "eckit/runtime/Main.h" #include "eckit/log/Log.h" #include "eckit/types/FloatCompare.h" #include "eckit/types/Types.h" #include "eckit/testing/Test.h" #ifdef __clang__ #pragma clang diagnostic ignored "-Wunused-function" #endif using namespace std; using namespace eckit; using namespace eckit::option; using namespace eckit::types; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- /// Test the options parser /// @note The options parser normall calls ::exit(1) if an error occurs. All the constructors accept an additional /// option to turn this behaviour off, and throw a UserError instead. /// @note Main can only be initialised once, so we need to generate a binary per test case via ifdefs namespace { /// A self-cleaning container for Option struct options_t : public std::vector { using vector::push_back; ~options_t() { for (auto* option : *this) { delete option; } } }; /// A local function to satisfy the CmdArg details void usage(const std::string&) { // Empty } void init(int nargs, const char* global_args[]) { Main::initialise(nargs, const_cast(global_args)); CmdArgs(&usage, 1, 0, true); } void init(int nargs, const char* global_args[], options_t& options, int args_count = 0) { Main::initialise(nargs, const_cast(global_args)); CmdArgs(&usage, options, args_count, 0, true); } } // namespace //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE >= 1 and TESTCASE <= 3 CASE("test_eckit_option_cmdargs_numbered_args_required") { // Argument parser will succeed when passed exactly one unnamed argument. // Note that argument 0 is always the executable name. #if TESTCASE == 1 const char* args1[] = {"exe"}; EXPECT_THROWS_AS(init(1, args1), UserError); #endif #if TESTCASE == 2 const char* args2[] = {"exe", "a1"}; EXPECT_NO_THROW(init(2, args2)); #endif #if TESTCASE == 3 const char* args3[] = {"exe", "a1", "a2"}; EXPECT_THROWS_AS(init(3, args3), UserError); #endif } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE >= 4 and TESTCASE <= 6 CASE("test_eckit_option_cmdargs_numbered_args_required_with_options") { options_t options; options.push_back(new SimpleOption("arg1", "")); // Argument parser will succeed when passed exactly one unnamed argument. // Note that argument 0 is always the executable name. #if TESTCASE == 4 const char* args1[] = {"exe"}; EXPECT_THROWS_AS(init(1, args1, options, 1), UserError); #endif #if TESTCASE == 5 const char* args2[] = {"exe", "a1"}; EXPECT_NO_THROW(init(2, args2, options, 1)); #endif #if TESTCASE == 6 const char* args3[] = {"exe", "a1", "a2"}; EXPECT_THROWS_AS(init(3, args3, options, 1), UserError); #endif } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 7 CASE("test_eckit_option_cmdargs_simple_argument_string") { // Set up he parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); const char* input[] = {"exe", "--arg1=testing"}; Main::initialise(2, const_cast(input)); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg1")); EXPECT(!args.has("arg2")); std::string tmpstr; args.get("arg1", tmpstr); EXPECT(tmpstr == "testing"); EXPECT(args.getString("arg1") == "testing"); } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 8 CASE("test_eckit_option_cmdargs_simple_argument_integer") { // Set up the parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); const char* input[] = {"exe", "--arg2=12345"}; Main::initialise(2, const_cast(input)); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg2")); EXPECT(!args.has("arg1")); long tmpi; args.get("arg2", tmpi); EXPECT(tmpi == 12345); EXPECT(args.getLong("arg2") == 12345); } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 9 CASE("test_eckit_option_cmdargs_simple_argument_missing") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); const char* input[] = {"exe", "--arg3=12345"}; EXPECT_THROWS_AS(init(2, input, options), UserError); } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 10 CASE("test_eckit_option_cmdargs_integer_vector") { // Set up the parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. options_t options; options.push_back(new VectorOption("arg", "", 3)); const char* input[] = {"exe", "--arg=-12345/678/-123"}; Main::initialise(2, const_cast(input)); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg")); std::vector tmpv; args.get("arg", tmpv); EXPECT(tmpv.size() == 3); EXPECT(tmpv[0] == -12345); EXPECT(tmpv[1] == 678); EXPECT(tmpv[2] == -123); EXPECT(tmpv == args.getLongVector("arg")); } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 11 CASE("test_eckit_option_cmdargs_double_vector") { // Set up the parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. options_t options; options.push_back(new VectorOption("arg", "", 4)); const char* input[] = {"exe", "--arg=-123.45/67.8/90/-123.0"}; Main::initialise(2, const_cast(input)); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg")); std::vector tmpv; args.get("arg", tmpv); EXPECT(tmpv.size() == 4); EXPECT(is_approximately_equal(tmpv[0], -123.45, 1.0e-8)); EXPECT(is_approximately_equal(tmpv[1], 67.8, 1.0e-8)); EXPECT(is_approximately_equal(tmpv[2], static_cast(90), 1.0e-8)); EXPECT(is_approximately_equal(tmpv[3], static_cast(-123), 1.0e-8)); EXPECT(tmpv == args.getDoubleVector("arg")); } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 12 CASE("test_eckit_option_cmdargs_vector_size_check") { options_t options; options.push_back(new VectorOption("arg", "", 4)); const char* input[] = {"exe", "--arg=1/2/3"}; EXPECT_THROWS_AS(init(2, input, options), UserError); } #endif //---------------------------------------------------------------------------------------------------------------------- #if TESTCASE == 13 CASE("test_eckit_option__string_with_default_value") { options_t options; options.push_back(new SimpleOption("arg1", "", "default-value")); std::vector input = {"exe"}; Main::initialise(input.size(), const_cast(&input[0]), nullptr); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); EXPECT(args.has("arg1")); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "default-value"); } #endif #if TESTCASE == 14 CASE("test_eckit_option__string_no_default_value") { options_t options; options.push_back(new SimpleOption("arg1", "")); std::vector input = {"exe"}; Main::initialise(input.size(), const_cast(&input[0]), nullptr); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); EXPECT(!args.has("arg1")); } #endif #if TESTCASE == 15 CASE("test_eckit_option__bool_plus_separated_string") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg2", "--arg1", "value"}; Main::initialise(input.size(), const_cast(&input[0]), nullptr); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg1")); EXPECT(args.has("arg2")); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "value"); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "value"); } { bool flag; args.get("arg2", flag); EXPECT_EQUAL(flag, true); auto arg2_actual = args.getBool("arg2"); EXPECT_EQUAL(arg2_actual, true); } } #endif #if TESTCASE == 16 CASE("test_eckit_option__bool_plus_joint_string") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg2", "--arg1=value"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg1")); EXPECT(args.has("arg2")); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "value"); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "value"); } { bool flag; args.get("arg2", flag); EXPECT_EQUAL(flag, true); auto arg2_actual = args.getBool("arg2"); EXPECT_EQUAL(arg2_actual, true); } } #endif #if TESTCASE == 17 CASE("test_eckit_option__long_plus_joint_string") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg2=12345", "--arg1=value"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.has("arg1")); EXPECT(args.has("arg2")); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "value"); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "value"); } { long i; args.get("arg2", i); EXPECT_EQUAL(i, 12345); auto arg2_actual = args.getLong("arg2"); EXPECT_EQUAL(arg2_actual, 12345L); } } #endif #if TESTCASE == 18 CASE("test_eckit_option__long_plus_joint_and_separated_string") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); options.push_back(new SimpleOption("arg3", "")); std::vector input = {"exe", "--arg1", "value1", "pos1", "pos2", "pos3", "--arg2=value2", "pos4", "--arg3=1234", "pos5"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 4, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 5); auto pos1_actual = args(0); EXPECT_EQUAL(pos1_actual, "pos1"); auto pos2_actual = args(1); EXPECT_EQUAL(pos2_actual, "pos2"); auto pos3_actual = args(2); EXPECT_EQUAL(pos3_actual, "pos3"); auto pos4_actual = args(3); EXPECT_EQUAL(pos4_actual, "pos4"); auto pos5_actual = args(4); EXPECT_EQUAL(pos5_actual, "pos5"); { EXPECT(args.has("arg1")); std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "value1"); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "value1"); } { EXPECT(args.has("arg2")); std::string s; args.get("arg2", s); EXPECT_EQUAL(s, "value2"); auto arg2_actual = args.getString("arg2"); EXPECT_EQUAL(arg2_actual, "value2"); } { EXPECT(args.has("arg3")); long i; args.get("arg3", i); EXPECT_EQUAL(i, 1234L); auto arg3_actual = args.getLong("arg3"); EXPECT_EQUAL(arg3_actual, 1234L); } } #endif #if TESTCASE == 19 CASE("test_eckit_option__multi_separate_value_alone") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2)); std::vector input = {"exe", "--arg1", "value1", "value2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } } } #endif #if TESTCASE == 20 CASE("test_eckit_option__multi_joint_value_alone") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2)); std::vector input = {"exe", "--arg1=value1", "value2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } } } #endif #if TESTCASE == 21 CASE("test_eckit_option__multi_value_no_default_value") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2)); std::vector input = {"exe", "pos1", "pos2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); EXPECT(!args.has("arg1")); } #endif #if TESTCASE == 22 CASE("test_eckit_option__multi_value_with_default_value") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2, MultiValueOption::values_t{"value1", "value2"})); std::vector input = {"exe", "pos1", "pos2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } } } #endif #if TESTCASE == 23 CASE("test_eckit_option__multi_value_with_1_mandatory_0_optional") { options_t options; options.push_back(new MultiValueOption("arg1", "", 1, 0)); std::vector input = {"exe", "--arg1", "value1", "pos1", "pos2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 1); EXPECT_EQUAL(v[0], "value1"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 1); EXPECT_EQUAL(v[0], "value1"); } } } #endif #if TESTCASE == 24 CASE("test_eckit_option__multi_value_with_2_mandatory_0_optional") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2, 0)); std::vector input = {"exe", "--arg1", "value1", "value2", "pos1", "pos2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); } } } #endif #if TESTCASE == 25 CASE("test_eckit_option__multi_value_with_2_mandatory_2_optional_2_provided") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2, 2)); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg1", "value1", "value2", "optional1", "optional2", "--arg2", "pos1", "pos2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 4); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); EXPECT_EQUAL(v[2], "optional1"); EXPECT_EQUAL(v[3], "optional2"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 4); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); EXPECT_EQUAL(v[2], "optional1"); EXPECT_EQUAL(v[3], "optional2"); } } EXPECT(args.has("arg2")); } #endif #if TESTCASE == 26 CASE("test_eckit_option__multi_value_with_2_mandatory_2_optional_1_provided_followed_by_another_option") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2, 2)); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg1", "value1", "value2", "optional1", "--arg2", "pos1", "pos2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 3); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); EXPECT_EQUAL(v[2], "optional1"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 3); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); EXPECT_EQUAL(v[2], "optional1"); } } EXPECT(args.has("arg2")); } #endif #if TESTCASE == 27 CASE("test_eckit_option__multi_value_with_2_mandatory_2_optional_1_provided_at_end") { options_t options; options.push_back(new MultiValueOption("arg1", "", 2, 2)); std::vector input = {"exe", "--arg1", "value1", "value2", "optional1"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); { std::vector v; args.get("arg1", v); EXPECT_EQUAL(v.size(), 3); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); EXPECT_EQUAL(v[2], "optional1"); } { std::vector v = args.getStringVector("arg1"); EXPECT_EQUAL(v.size(), 3); EXPECT_EQUAL(v[0], "value1"); EXPECT_EQUAL(v[1], "value2"); EXPECT_EQUAL(v[2], "optional1"); } } } #endif #if TESTCASE == 28 CASE("test_eckit_option__multi_value_with_others") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); options.push_back(new MultiValueOption("label", "", 2)); std::vector input = {"exe", "--arg1", "value1", "pos1", "pos2", "pos3", "--arg2=value2", "pos4", "--label=somelabel", "--a-label-value--", "pos5"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 4, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 5); auto pos1_actual = args(0); EXPECT_EQUAL(pos1_actual, "pos1"); auto pos2_actual = args(1); EXPECT_EQUAL(pos2_actual, "pos2"); auto pos3_actual = args(2); EXPECT_EQUAL(pos3_actual, "pos3"); auto pos4_actual = args(3); EXPECT_EQUAL(pos4_actual, "pos4"); auto pos5_actual = args(4); EXPECT_EQUAL(pos5_actual, "pos5"); { EXPECT(args.has("arg1")); std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "value1"); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "value1"); } { EXPECT(args.has("arg2")); std::string s; args.get("arg2", s); EXPECT_EQUAL(s, "value2"); auto arg2_actual = args.getString("arg2"); EXPECT_EQUAL(arg2_actual, "value2"); } { EXPECT(args.has("label")); { std::vector v; args.get("label", v); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "somelabel"); EXPECT_EQUAL(v[1], "--a-label-value--"); } { std::vector v = args.getStringVector("label"); EXPECT_EQUAL(v.size(), 2); EXPECT_EQUAL(v[0], "somelabel"); EXPECT_EQUAL(v[1], "--a-label-value--"); } } } #endif #if TESTCASE == 29 CASE("test_eckit_option__with_separator") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new Separator("Separator")); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg1", "value1", "--arg2=value2"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "value1"); auto arg1_actual = args.getString("arg1"); EXPECT_EQUAL(arg1_actual, "value1"); } { EXPECT(args.has("arg2")); std::string s; args.get("arg2", s); EXPECT_EQUAL(s, "value2"); auto arg2_actual = args.getString("arg2"); EXPECT_EQUAL(arg2_actual, "value2"); } } #endif #if TESTCASE == 30 CASE("test_eckit_option__with_separator_and_some_invalid_option_to_force_logging_usage") { options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new Separator("Separator")); options.push_back(new SimpleOption("arg2", "")); std::vector input = {"exe", "--arg1", "value1", "--arg2=value2", "--invalid=!!!"}; Main::initialise(input.size(), const_cast(&input[0])); try { CmdArgs args(&usage, options, 0, 0, true); EXPECT(false); } catch (const Exception& e) { EXPECT(std::string(e.what()).find("UserError: An error occurred in argument parsing") != std::string::npos); } } #endif #if TESTCASE == 31 CASE("test_eckit_option__with_simple_string_value_containing_equals_sign") { options_t options; options.push_back(new SimpleOption("arg1", "description")); std::vector input = {"exe", "--arg1=some=value=with=equals", "/some/path", "/another/path"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, -1, 2, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 2); { EXPECT(args.has("arg1")); std::string s; args.get("arg1", s); EXPECT_EQUAL(s, "some=value=with=equals"); } } #endif #if TESTCASE == 32 CASE("test_eckit_option__can_handle_bool_option_without_value") { options_t options; options.push_back(new SimpleOption("arg1", "description")); std::vector input = { "exe", "--arg1", }; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); bool v = args.getBool("arg1"); EXPECT_EQUAL(v, true); } } #endif #if TESTCASE == 33 CASE("test_eckit_option__can_handle_bool_option_with_true_value") { options_t options; options.push_back(new SimpleOption("arg1", "description")); std::vector input = { "exe", "--arg1=yes", }; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); bool v = args.getBool("arg1"); EXPECT_EQUAL(v, true); } } #endif #if TESTCASE == 34 CASE("test_eckit_option__can_handle_bool_option_with_false_value") { options_t options; options.push_back(new SimpleOption("arg1", "description")); std::vector input = { "exe", "--arg1=0", }; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); auto args_count = args.count(); EXPECT_EQUAL(args_count, 0); { EXPECT(args.has("arg1")); bool v = args.getBool("arg1"); EXPECT_EQUAL(v, false); } } #endif #if TESTCASE == 35 CASE("test_eckit_option__can_handle_simple_option_provided_twice") { options_t options; options.push_back(new SimpleOption("arg1", "description")); std::vector input = { "exe", "--arg1=first.value", "--arg1", "second.value", "--arg1=last.value", }; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); { EXPECT(args.has("arg1")); std::string s = args.getString("arg1"); EXPECT_EQUAL(s, "last.value"); } } #endif #if TESTCASE == 36 CASE("test_eckit_option__can_handle_vector_option_of_double") { options_t options; options.push_back(new VectorOption("arg1", "description", 2)); std::vector input = {"exe", "--arg1=-32.0/10"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); { EXPECT(args.has("arg1")); auto s = args.getDoubleVector("arg1"); EXPECT_EQUAL(s.size(), 2); } } #endif #if TESTCASE == 37 CASE("test_eckit_option_cmdargs_value_with_equals") { options_t options; for (auto c : std::string{"abcdefghijk"}) { options.push_back(new SimpleOption(std::string{c}, "")); } std::vector input = {"exe", "--a=a", "--b==b", "--c=c=", "--d===d", "--e==e=", "--f=f==", "--g=g=g", "--h==h=h", "--i==ii=", "--j=j==j", "--k=k=k="}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); EXPECT(args.getString("a") == "a"); EXPECT(args.getString("b") == "=b"); EXPECT(args.getString("c") == "c="); EXPECT(args.getString("d") == "==d"); EXPECT(args.getString("e") == "=e="); EXPECT(args.getString("f") == "f=="); EXPECT(args.getString("g") == "g=g"); EXPECT(args.getString("h") == "=h=h"); EXPECT(args.getString("i") == "=ii="); EXPECT(args.getString("j") == "j==j"); EXPECT(args.getString("k") == "k=k="); } #endif #if TESTCASE == 38 CASE("test_eckit_option__allows_to_set_default_value_for_options") { options_t options; options.push_back(new SimpleOption("arg1", "", "default string")); options.push_back( new VectorOption("arg2", "", 0, std::vector{"q", "w", "e", "r", "t", "y"})); options.push_back(new VectorOption("arg3", "", 0, std::vector{1, 2, 3, 4})); std::vector input = {"exe"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); { EXPECT(args.has("arg1")); auto v = args.getString("arg1"); EXPECT_EQUAL(v, "default string"); } { EXPECT(args.has("arg2")); auto v = args.getStringVector("arg2"); EXPECT_EQUAL(v.size(), 6); EXPECT_EQUAL(v[0], "q"); EXPECT_EQUAL(v[1], "w"); EXPECT_EQUAL(v[2], "e"); EXPECT_EQUAL(v[3], "r"); EXPECT_EQUAL(v[4], "t"); EXPECT_EQUAL(v[5], "y"); } { EXPECT(args.has("arg3")); auto v = args.getLongVector("arg3"); EXPECT_EQUAL(v.size(), 4); EXPECT_EQUAL(v[0], 1L); EXPECT_EQUAL(v[1], 2L); EXPECT_EQUAL(v[2], 3L); EXPECT_EQUAL(v[3], 4L); } } #endif #if TESTCASE == 39 CASE("test_eckit_option__cmdargs_argument_with_dot_in_option_name") { options_t options; options.push_back(new SimpleOption("a.a", "")); options.push_back(new SimpleOption("b.b.b", "")); options.push_back(new SimpleOption("c.", "")); options.push_back(new SimpleOption(".d", "")); std::vector input = {"exe", "--a.a=a", "--b.b.b=b", "--c.=c", "--.d=d"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); Log::info() << args << std::endl; std::string a; EXPECT(args.get("a.a", a)); EXPECT(a == "a"); EXPECT(args.getString("a.a") == a); std::string b; EXPECT(args.get("b.b.b", b)); EXPECT(b == "b"); EXPECT(args.getString("b.b.b") == b); std::string c; EXPECT(args.get("c.", c)); EXPECT(c == "c"); EXPECT(args.getString("c.") == c); std::string d; EXPECT(args.get(".d", d)); EXPECT(d == "d"); EXPECT(args.getString(".d") == d); } #endif #if TESTCASE == 40 CASE("test_eckit_option__cmdargs_argument_with_dot_in_option_name_and_value") { options_t options; options.push_back(new SimpleOption("a.a", "")); options.push_back(new SimpleOption("b.b.b", "")); options.push_back(new SimpleOption("c.", "")); options.push_back(new SimpleOption(".d", "")); std::vector input = {"exe", "--a.a=a.a", "--b.b.b=b.b.b", "--c.=c.", "--.d=.d"}; Main::initialise(input.size(), const_cast(&input[0])); CmdArgs args(&usage, options, 0, 0, true); Log::info() << args << std::endl; std::string a; EXPECT(args.get("a.a", a)); EXPECT(a == "a.a"); EXPECT(args.getString("a.a") == a); std::string b; EXPECT(args.get("b.b.b", b)); EXPECT(b == "b.b.b"); EXPECT(args.getString("b.b.b") == b); std::string c; EXPECT(args.get("c.", c)); EXPECT(c == "c."); EXPECT(args.getString("c.") == c); std::string d; EXPECT(args.get(".d", d)); EXPECT(d == ".d"); EXPECT(args.getString(".d") == d); } #endif //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv, false); // will not call main::initialize } eckit-2.3.0/tests/codec/0000775000175000017500000000000015253173651015226 5ustar alastairalastaireckit-2.3.0/tests/codec/test_codec_record.cc0000664000175000017500000005324515253173651021220 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/codec/codec.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/MemoryHandle.h" #include "eckit/testing/Test.h" namespace eckit::test { template struct Matrix { std::vector data_; size_t rows_; size_t cols_; size_t size() const { return rows_ * cols_; } T* data() { return data_.data(); } const T* data() const { return data_.data(); } codec::DataType datatype() const { return codec::make_datatype(); } Matrix(size_t rows, size_t cols) { resize(rows, cols); } void resize(size_t rows, size_t cols) { rows_ = rows; cols_ = cols; data_.resize(size()); } void assign(std::initializer_list list) { data_.assign(list); } void assign(T value) { data_.assign(size(), value); } }; template void interprete(const Matrix& in, codec::ArrayReference& out) { out = codec::ArrayReference(in.data(), in.datatype(), codec::ArrayShape{in.rows_, in.cols_}); } template void decode(const codec::Metadata& metadata, const codec::Data& data, Matrix& out) { codec::ArrayMetadata array(metadata); out.resize(array.shape(0), array.shape(1)); ::memcpy(out.data(), data, data.size()); } struct Arrays { std::vector v1; std::vector v2; Matrix v3{0, 0}; bool operator==(const Arrays& other) const { return v1 == other.v1 && ::memcmp(v2.data(), other.v2.data(), v2.size() * sizeof(float)) == 0 && ::memcmp(v3.data(), other.v3.data(), v3.size() * v3.datatype().size()) == 0; } bool operator!=(const Arrays& other) const { return not operator==(other); } }; //----------------------------------------------------------------------------- static eckit::LocalConfiguration no_compression = [] { eckit::LocalConfiguration c; c.set("compression", "none"); return c; }(); //----------------------------------------------------------------------------- std::string suffix() { static std::string suffix = eckit::Resource("--suffix", ""); return suffix; } //----------------------------------------------------------------------------- namespace globals { struct TestRecord { Arrays data; TestRecord() = default; explicit TestRecord(const std::function& initializer) { initializer(data); } }; static TestRecord record1{[](Arrays& data) { data.v1 = {0, 1, 2, 3, 4}; data.v2 = {3, 2, 1}; data.v3.resize(3, 2); data.v3.assign({11, 12, 21, 22, 31, 32}); }}; static TestRecord record2{[](Arrays& data) { data.v1 = {0, 10, 20, 30, 40, 50}; data.v2 = {30, 20, 10, 40}; data.v3.resize(2, 3); data.v3.assign({11, 12, 13, 21, 22, 23}); }}; static TestRecord record3{[](Arrays& data) { data.v1.assign(1024 / 8 - 1, 2.); data.v2.assign(1023 * 1024 / 4 + 512 / 4, 1.); data.v3.resize(1024, 1024); data.v3.assign(3); }}; std::vector records; } // namespace globals //----------------------------------------------------------------------------- template void write_length(Length length, const std::string& path) { std::ofstream file(path); file << length; file.close(); } //-----------------------------------------------------------------------------// // // // Writing records // // // //-----------------------------------------------------------------------------// CASE("Write records, each in separate file (offset=0)") { auto write_record = [&](const Arrays& data, const eckit::PathName& path) { codec::RecordWriter record; record.set("v1", codec::ref(data.v1), no_compression); record.set("v2", codec::ref(data.v2), no_compression); record.set("v3", codec::ref(data.v3)); auto length = record.write(path); write_length(length, path + ".length"); }; SECTION("record1.atlas" + suffix()) { write_record(globals::record1.data, "record1.atlas" + suffix()); } SECTION("record2.atlas" + suffix()) { write_record(globals::record2.data, "record2.atlas" + suffix()); } SECTION("record3.atlas" + suffix()) { write_record(globals::record3.data, "record3.atlas" + suffix()); } } //----------------------------------------------------------------------------- CASE("Write records to same file using record.write(path,codec::Mode)") { // This will reopen files upon every append, bad for performance static std::vector lengths; static std::vector offsets{0}; auto write_record = [&](Arrays& data, const eckit::PathName& path, codec::Mode mode) { codec::RecordWriter record; record.set("v1", codec::ref(data.v1), no_compression); record.set("v2", codec::ref(data.v2), no_compression); record.set("v3", codec::ref(data.v3)); globals::records.emplace_back(path, offsets.back()); lengths.emplace_back(record.write(path, mode)); offsets.emplace_back(offsets.back() + lengths.back()); }; SECTION("record1 -> records.atlas" + suffix()) { write_record(globals::record1.data, "records.atlas" + suffix(), codec::Mode::write); } SECTION("record2 -> records.atlas" + suffix()) { write_record(globals::record2.data, "records.atlas" + suffix(), codec::Mode::append); } SECTION("record3 -> records.atlas" + suffix()) { write_record(globals::record3.data, "records.atlas" + suffix(), codec::Mode::append); } } //----------------------------------------------------------------------------- CASE("Write records to same file using record.write(Stream) keeping Stream open") { // This should give exactly the same output file as previous, except no auto write_record = [&](const Arrays& data, codec::Stream stream) { codec::RecordWriter record; record.set("v1", codec::ref(data.v1), no_compression); record.set("v2", codec::ref(data.v2), no_compression); record.set("v3", codec::ref(data.v3)); record.write(stream); }; static codec::OutputFileStream stream("records.atlas" + suffix() + ".duplicate"); SECTION("record1 -> records.atlas" + suffix() + ".duplicate") { EXPECT_EQUAL(stream.position(), globals::records[0].offset); write_record(globals::record1.data, stream); } SECTION("record2 -> records.atlas" + suffix() + ".duplicate") { EXPECT_EQUAL(stream.position(), globals::records[1].offset); write_record(globals::record2.data, stream); } SECTION("record3 -> records.atlas" + suffix() + ".duplicate") { EXPECT_EQUAL(stream.position(), globals::records[2].offset); write_record(globals::record3.data, stream); } SECTION("close stream") { stream.close(); // required because stream is a static variable } } //----------------------------------------------------------------------------- CASE("Write master record referencing record1 and record2 and record3") { codec::RecordWriter record; record.set("v1", codec::link("file:record1.atlas" + suffix() + "?key=v1")); record.set("v2", codec::link("file:record1.atlas" + suffix() + "?key=v2")); record.set("v3", codec::link("file:record1.atlas" + suffix() + "?key=v3")); record.set("v4", codec::link("file:record2.atlas" + suffix() + "?key=v1")); record.set("v5", codec::link("file:record2.atlas" + suffix() + "?key=v2")); record.set("v6", codec::link("file:record2.atlas" + suffix() + "?key=v3")); record.set("v7", codec::link("file:record3.atlas" + suffix() + "?key=v1")); record.set("v8", codec::link("file:record3.atlas" + suffix() + "?key=v2")); record.set("v9", codec::link("file:record3.atlas" + suffix() + "?key=v3")); record.write("record.atlas" + suffix()); } //----------------------------------------------------------------------------- CASE("Write records in nested subdirectories") { auto reference_path = eckit::PathName{"atlas_test_io_refpath"}; { eckit::PathName{reference_path / "links" / "1"}.mkdir(); codec::RecordWriter record; record.set("v1", codec::ref(globals::record1.data.v1)); record.set("v2", codec::ref(globals::record1.data.v2)); record.set("v3", codec::ref(globals::record1.data.v3)); record.set("s1", std::string("short string")); record.set("s2", (1. / 3.)); record.write(reference_path / "links" / "1" / "record.atlas" + suffix()); } { eckit::PathName{reference_path / "links" / "2"}.mkdir(); codec::RecordWriter record; record.set("v1", codec::ref(globals::record2.data.v1)); record.set("v2", codec::ref(globals::record2.data.v2)); record.set("v3", codec::ref(globals::record2.data.v3)); record.set("s1", static_cast(10000000000)); record.write(reference_path / "links" / "2" / "record.atlas" + suffix()); } { codec::RecordWriter record; record.set("l1", codec::link("file:1/record.atlas" + suffix() + "?key=v1")); record.set("l2", codec::link("file:1/record.atlas" + suffix() + "?key=v2")); record.set("l3", codec::link("file:1/record.atlas" + suffix() + "?key=v3")); record.set("l4", codec::link("file:2/record.atlas" + suffix() + "?key=v1")); record.set("l5", codec::link("file:2/record.atlas" + suffix() + "?key=v2")); record.set("l6", codec::link("file:2/record.atlas" + suffix() + "?key=v3")); record.set("l7", codec::link("file:1/record.atlas" + suffix() + "?key=s1")); record.set("l8", codec::link("file:1/record.atlas" + suffix() + "?key=s2")); record.set("l9", codec::link("file:2/record.atlas" + suffix() + "?key=s1")); record.write(reference_path / "links" / "record.atlas" + suffix()); } { codec::RecordWriter record; record.set("l1", codec::link("file:links/record.atlas" + suffix() + "?key=l1")); record.set("l2", codec::link("file:links/record.atlas" + suffix() + "?key=l2")); record.set("l3", codec::link("file:links/record.atlas" + suffix() + "?key=l3")); record.set("l4", codec::link("file:links/record.atlas" + suffix() + "?key=l4")); record.set("l5", codec::link("file:links/record.atlas" + suffix() + "?key=l5")); record.set("l6", codec::link("file:links/record.atlas" + suffix() + "?key=l6")); record.set("l7", codec::link("file:links/record.atlas" + suffix() + "?key=l7")); record.set("l8", codec::link("file:links/record.atlas" + suffix() + "?key=l8")); record.set("l9", codec::link("file:links/record.atlas" + suffix() + "?key=l9")); record.write(reference_path / "record.atlas" + suffix()); } } //-----------------------------------------------------------------------------// // // // Reading tests // // // //-----------------------------------------------------------------------------// CASE("Test RecordItemReader") { SECTION("file:record1.atlas" + suffix() + "?key=v2") { codec::RecordItemReader reader{"file:record1.atlas" + suffix() + "?key=v2"}; { // When we only want to read metadata codec::Metadata metadata; reader.read(metadata); EXPECT(metadata.link() == false); EXPECT_EQUAL(metadata.type(), "array"); EXPECT(metadata.data.compressed() == false); EXPECT_EQUAL(metadata.data.compression(), "none"); EXPECT_EQUAL(metadata.data.size(), globals::record1.data.v2.size() * sizeof(float)); } { // When we want to read both metadata and data codec::Metadata metadata; codec::Data data; reader.read(metadata, data); EXPECT(metadata.data.compressed() == false); EXPECT_EQUAL(metadata.data.compression(), "none"); EXPECT_EQUAL(metadata.data.size(), globals::record1.data.v2.size() * sizeof(float)); EXPECT(data.size() == metadata.data.size()); EXPECT(data.size() == metadata.data.compressed_size()); EXPECT(::memcmp(data, globals::record1.data.v2.data(), data.size()) == 0); } } SECTION("file:record.atlas" + suffix() + "?key=v9") { codec::RecordItemReader reader{"file:record.atlas" + suffix() + "?key=v9"}; { // When we only want to read metadata codec::Metadata metadata; reader.read(metadata); EXPECT(metadata.link() == true); EXPECT_EQUAL(metadata.link().str(), "file:record3.atlas" + suffix() + "?key=v3"); EXPECT_EQUAL(metadata.type(), "array"); } { // When we want to read both metadata and data codec::Metadata metadata; codec::Data data; reader.read(metadata, data); EXPECT(metadata.data.compressed() == (codec::defaults::compression_algorithm() != "none")); EXPECT_EQUAL(metadata.data.compression(), codec::defaults::compression_algorithm()); EXPECT_EQUAL(metadata.data.size(), globals::record3.data.v3.size() * sizeof(int)); EXPECT(data.size() == metadata.data.compressed_size()); } } } //----------------------------------------------------------------------------- CASE("Read records from different files") { Arrays data1; Arrays data2; Arrays data3; auto read_record = [&](const eckit::PathName& path, Arrays& data) { codec::RecordReader record(path); record.read("v1", data.v1).wait(); record.read("v2", data.v2).wait(); record.read("v3", data.v3).wait(); }; read_record("record1.atlas" + suffix(), data1); read_record("record2.atlas" + suffix(), data2); read_record("record3.atlas" + suffix(), data3); EXPECT(data1 == globals::record1.data); EXPECT(data2 == globals::record2.data); EXPECT(data3 == globals::record3.data); } //----------------------------------------------------------------------------- CASE("Read multiple records from same file") { Arrays data1; Arrays data2; codec::RecordReader record1(globals::records[0]); codec::RecordReader record2(globals::records[1]); record1.read("v1", data1.v1).wait(); record1.read("v2", data1.v2).wait(); record1.read("v3", data1.v3).wait(); record2.read("v1", data2.v1).wait(); record2.read("v2", data2.v2).wait(); record2.read("v3", data2.v3).wait(); EXPECT(data1 == globals::record1.data); EXPECT(data2 == globals::record2.data); } //----------------------------------------------------------------------------- CASE("Write master record referencing record1 and record2") { codec::RecordWriter record; record.set("v1", codec::link("file:record1.atlas" + suffix() + "?key=v1")); record.set("v2", codec::link("file:record1.atlas" + suffix() + "?key=v2")); record.set("v3", codec::link("file:record1.atlas" + suffix() + "?key=v3")); record.set("v4", codec::link("file:record2.atlas" + suffix() + "?key=v1")); record.set("v5", codec::link("file:record2.atlas" + suffix() + "?key=v2")); record.set("v6", codec::link("file:record2.atlas" + suffix() + "?key=v3")); record.write("record.atlas" + suffix()); } //----------------------------------------------------------------------------- CASE("Read master record") { Arrays data1; Arrays data2; codec::RecordReader record("record.atlas" + suffix()); eckit::Log::info() << "record.metadata(\"v1\"): " << record.metadata("v1") << std::endl; record.read("v1", data1.v1).wait(); record.read("v2", data1.v2).wait(); record.read("v3", data1.v3).wait(); record.read("v4", data2.v1).wait(); record.read("v5", data2.v2).wait(); record.read("v6", data2.v3).wait(); EXPECT(data1 == globals::record1.data); EXPECT(data2 == globals::record2.data); } //----------------------------------------------------------------------------- CASE("Async read") { Arrays data1; Arrays data2; codec::RecordReader record("record.atlas" + suffix()); // Request reads record.read("v1", data1.v1); record.read("v2", data1.v2); record.read("v3", data1.v3); record.read("v4", data2.v1); record.read("v5", data2.v2); record.read("v6", data2.v3); // Wait for specific requests record.wait("v4"); record.wait("v5"); record.wait("v6"); // Should have completed EXPECT(data2 == globals::record2.data); // Should not be complete yet EXPECT(data1 != globals::record1.data); // Wait for all requests; record.wait(); // Should have completed EXPECT(data1 == globals::record1.data); } //----------------------------------------------------------------------------- CASE("Recursive Write/read records in nested subdirectories") { auto reference_path = eckit::PathName{"atlas_test_io_refpath"}; // Read Arrays data1; Arrays data2; codec::RecordReader record(reference_path / "record.atlas" + suffix()); record.read("l1", data1.v1).wait(); record.read("l2", data1.v2).wait(); record.read("l3", data1.v3).wait(); record.read("l4", data2.v1).wait(); record.read("l5", data2.v2).wait(); record.read("l6", data2.v3).wait(); std::string l7; double l8; size_t l9; record.read("l7", l7).wait(); record.read("l8", l8).wait(); record.read("l9", l9).wait(); EXPECT(data1 == globals::record1.data); EXPECT(data2 == globals::record2.data); EXPECT_EQUAL(l7, "short string"); EXPECT_EQUAL(l8, 1. / 3.); EXPECT_EQUAL(l9, 10000000000UL); } //----------------------------------------------------------------------------- CASE("Write record to memory") { const auto& data_write = globals::record3.data; const auto& v1 = globals::record3.data.v1; const auto& v2 = globals::record3.data.v2; const auto& v3 = globals::record3.data.v3; eckit::Buffer memory; // write { codec::RecordWriter record; record.compression(false); record.checksum(false); record.set("v1", codec::ref(v1)); record.set("v2", codec::ref(v2)); record.set("v3", codec::ref(v3)); memory.resize(record.estimateMaximumSize()); eckit::Log::info() << "memory.size() : " << memory.size() << std::endl; ; eckit::MemoryHandle datahandle_out{memory}; datahandle_out.openForWrite(0); auto record_length = record.write(datahandle_out); datahandle_out.close(); // Without compression, this should be exact EXPECT(memory.size() == record_length); } // read with individual RecordItemReader { codec::Session session; eckit::MemoryHandle datahandle_in{memory}; datahandle_in.openForRead(); { codec::RecordItemReader reader(datahandle_in, "v1"); codec::Metadata metadata; codec::Data data; reader.read(metadata, data); EXPECT(::memcmp(data, data_write.v1.data(), data.size()) == 0); } { codec::RecordItemReader reader(datahandle_in, "v2"); codec::Metadata metadata; codec::Data data; reader.read(metadata, data); EXPECT(::memcmp(data, data_write.v2.data(), data.size()) == 0); } { codec::RecordItemReader reader(datahandle_in, "v3"); codec::Metadata metadata; codec::Data data; reader.read(metadata, data); EXPECT(::memcmp(data, data_write.v3.data(), data.size()) == 0); } datahandle_in.close(); } // read with RecordReader { Arrays data_read; eckit::MemoryHandle datahandle_in{memory}; datahandle_in.openForRead(); codec::RecordReader reader(datahandle_in); reader.read("v1", data_read.v1); reader.read("v2", data_read.v2); reader.read("v3", data_read.v3); reader.wait(); datahandle_in.close(); EXPECT(data_read == data_write); } } //-----------------------------------------------------------------------------// // // // Reading tests // // // //-----------------------------------------------------------------------------// CASE("RecordPrinter") { SECTION("table") { eckit::LocalConfiguration table_with_details; table_with_details.set("format", "table"); table_with_details.set("details", true); codec::RecordPrinter record{eckit::PathName("record1.atlas" + suffix()), table_with_details}; std::stringstream out; EXPECT_NO_THROW(out << record); eckit::Log::debug() << out.str(); } SECTION("yaml") { eckit::LocalConfiguration yaml_with_details; yaml_with_details.set("format", "yaml"); yaml_with_details.set("details", true); codec::RecordPrinter record{eckit::PathName("record1.atlas" + suffix()), yaml_with_details}; std::stringstream out; EXPECT_NO_THROW(out << record); eckit::Log::debug() << out.str(); } } //----------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/codec/test_codec_encoding.cc0000664000175000017500000004777715253173651021545 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include "eckit/codec/Data.h" #include "eckit/codec/codec.h" #include "eckit/testing/Test.h" namespace eckit::test { // ------------------------------------------------------------------------------------------------------- struct UnencodableType { std::string _; }; // ------------------------------------------------------------------------------------------------------- // Example type that can be encoded / decoded with eckit::codec // The "operations" performed on this type are stored for unit-test purposes class EncodableType { public: using Operations = std::vector; explicit EncodableType(std::string s, std::shared_ptr operations = std::make_shared()) : str(s), ops(operations) { ops->emplace_back("constructor"); } EncodableType() : ops(std::make_shared()) {} EncodableType(const EncodableType& other) : str(other.str), ops(other.ops) { // This constructor should not be called. ops->emplace_back("copy constructor"); } EncodableType(EncodableType&& other) : str(std::move(other.str)), ops(other.ops) { ops->emplace_back("move constructor"); } EncodableType& operator=(const EncodableType& rhs) { if (this == &rhs) { return *this; } // This assignment should not be called. str = rhs.str; ops = rhs.ops; ops->emplace_back("assignment"); return *this; } EncodableType& operator=(EncodableType&& rhs) { // This assignment should not be called. str = std::move(rhs.str); ops = rhs.ops; ops->emplace_back("move"); return *this; } friend void encode_data(const EncodableType& in, codec::Data& out) { in.ops->emplace_back("encode_data"); out.assign(in.str.data(), in.str.size()); } friend size_t encode_metadata(const EncodableType& in, codec::Metadata& metadata) { in.ops->emplace_back("encode_metadata"); metadata.set("type", "EncodableType"); metadata.set("bytes", in.str.size()); return in.str.size(); } friend void decode(const codec::Metadata&, const codec::Data& b, EncodableType& out) { out.ops->emplace_back("decode"); const auto* data = static_cast(b.data()); out.str = std::string(data, data + b.size()); } const std::vector& operations() const { return *ops; } private: std::string str; mutable std::shared_ptr> ops; }; // ------------------------------------------------------------------------------------------------------- CASE("test exceptions") { EXPECT(not(codec::is_interpretable())); EXPECT(not codec::is_encodable()); EXPECT(not codec::can_encode_metadata()); EXPECT(not codec::can_encode_data()); UnencodableType in; codec::Data data; codec::Metadata metadata; EXPECT_THROWS_AS(codec::ref(in, codec::tag::disable_static_assert()), codec::NotEncodable); EXPECT_THROWS_AS(codec::copy(in, codec::tag::disable_static_assert()), codec::NotEncodable); EXPECT_THROWS_AS(codec::encode(in, metadata, data, codec::tag::disable_static_assert()), codec::NotEncodable); } // ------------------------------------------------------------------------------------------------------- CASE("encoding test::EncodableType") { static_assert(not codec::is_interpretable()); static_assert(codec::is_encodable()); static_assert(codec::is_decodable()); const std::string encoded_string{"encoded string"}; EncodableType in(encoded_string); codec::Data data; codec::Metadata metadata; EXPECT_NO_THROW(encode(in, metadata, data)); EXPECT(metadata.type() == "EncodableType"); EXPECT(data.size() == encoded_string.size()); EXPECT(::memcmp(data, encoded_string.data(), encoded_string.size()) == 0); } // ------------------------------------------------------------------------------------------------------- CASE("encoding codec::types::ArrayView") { static_assert(not codec::is_interpretable()); static_assert(codec::can_encode_data()); static_assert(codec::can_encode_metadata()); static_assert(codec::is_encodable()); } // ------------------------------------------------------------------------------------------------------- template void assert_StdVector() { static_assert(codec::is_interpretable, codec::ArrayReference>()); static_assert(not codec::can_encode_data>()); static_assert(not codec::can_encode_metadata>()); static_assert(not codec::is_encodable>()); } template void encode_StdVector() { std::vector in{1, 2, 3, 4, 5}; codec::ArrayReference interpreted; interprete(in, interpreted); codec::Data data; codec::Metadata metadata; encode(interpreted, metadata, data); EXPECT(data.size() == in.size() * sizeof(T)); EXPECT(::memcmp(in.data(), data.data(), data.size()) == 0); EXPECT(metadata.type() == "array"); EXPECT(metadata.getString("datatype") == codec::DataType::str()); } CASE("encoding std::vector") { assert_StdVector(); assert_StdVector(); assert_StdVector(); assert_StdVector(); assert_StdVector(); encode_StdVector(); encode_StdVector(); encode_StdVector(); encode_StdVector(); { using T = std::byte; std::bitset<8> bits; std::vector in; in.resize(5); size_t n{0}; for (auto& byte : in) { bits.set(n++, true); byte = *reinterpret_cast(&bits); } codec::ArrayReference interpreted; interprete(in, interpreted); codec::Data data; codec::Metadata metadata; encode(interpreted, metadata, data); EXPECT(data.size() == in.size() * sizeof(T)); EXPECT(::memcmp(in.data(), data.data(), data.size()) == 0); EXPECT(metadata.type() == "array"); EXPECT(metadata.getString("datatype") == codec::DataType::str()); } } // ------------------------------------------------------------------------------------------------------- template void assert_StdArray() { static_assert(codec::is_interpretable, codec::ArrayReference>()); static_assert(not codec::can_encode_data>()); static_assert(not codec::can_encode_metadata>()); static_assert(not codec::is_encodable>()); } template void encode_StdArray() { std::array in{1, 2, 3, 4, 5}; codec::ArrayReference interpreted; interprete(in, interpreted); codec::Data data; codec::Metadata metadata; encode(interpreted, metadata, data); EXPECT(data.size() == in.size() * sizeof(T)); EXPECT(::memcmp(in.data(), data.data(), data.size()) == 0); EXPECT(metadata.type() == "array"); EXPECT(metadata.getString("datatype") == codec::DataType::str()); } CASE("encoding std::array") { assert_StdArray(); assert_StdArray(); assert_StdArray(); assert_StdArray(); assert_StdArray(); encode_StdVector(); encode_StdVector(); encode_StdVector(); encode_StdVector(); { using T = std::byte; std::bitset<8> bits; std::vector in; in.resize(5); size_t n{0}; for (auto& byte : in) { bits.set(n++, true); byte = *reinterpret_cast(&bits); } codec::ArrayReference interpreted; interprete(in, interpreted); codec::Data data; codec::Metadata metadata; encode(interpreted, metadata, data); EXPECT(data.size() == in.size() * sizeof(T)); EXPECT(::memcmp(in.data(), data.data(), data.size()) == 0); EXPECT(metadata.type() == "array"); EXPECT(metadata.getString("datatype") == codec::DataType::str()); } } // ------------------------------------------------------------------------------------------------------- CASE("test Encoder") { SECTION("default constructor") { codec::Encoder encoder; EXPECT(encoder == false); codec::Metadata metadata; codec::Data data; EXPECT_THROWS_AS(encode(encoder, metadata, data), eckit::AssertionFailed); } SECTION("Encoder via reference") { codec::Encoder encoder; auto ops = std::make_shared(); EncodableType encodable("string", ops); EXPECT_EQUAL(ops->size(), 1); EXPECT_EQUAL(ops->back(), "constructor"); codec::ref(encodable); EXPECT_EQUAL(ops->size(), 1); EXPECT_EQUAL(ops->back(), "constructor"); encoder = codec::Encoder{codec::ref(encodable)}; EXPECT_EQUAL(ops->size(), 2); EXPECT_EQUAL(ops->back(), "encode_metadata"); codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT_EQUAL(ops->size(), 3); EXPECT_EQUAL(ops->back(), "encode_data"); } SECTION("Encoder via copy") { codec::Encoder encoder; auto ops = std::make_shared(); EncodableType encodable("string", ops); EXPECT_EQUAL(ops->size(), 1); EXPECT_EQUAL(ops->back(), "constructor"); encoder = codec::Encoder{codec::copy(encodable)}; EXPECT_EQUAL(ops->size(), 3); EXPECT_EQUAL(ops->at(1), "encode_metadata"); EXPECT_EQUAL(ops->at(2), "encode_data"); codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT_EQUAL(ops->size(), 3); EXPECT_EQUAL(ops->at(2), "encode_data"); } SECTION("Encoder via move") { codec::Encoder encoder; auto ops = std::make_shared(); EncodableType encodable("string", ops); EXPECT_EQUAL(ops->size(), 1); EXPECT_EQUAL(ops->back(), "constructor"); encoder = codec::Encoder{std::move(encodable)}; EXPECT_EQUAL(ops->size(), 3); EXPECT_EQUAL(ops->at(1), "move constructor"); EXPECT_EQUAL(ops->at(2), "encode_metadata"); codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT_EQUAL(ops->size(), 4); EXPECT_EQUAL(ops->at(3), "encode_data"); } } // ------------------------------------------------------------------------------------------------------- CASE("Encoder for std::vector") { SECTION("ref") { using T = double; std::vector v{1, 2, 3, 4, 5, 6, 7, 8}; codec::Encoder encoder(codec::ref(v)); // We can only encode with reference to original vector (no copies were made) codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT(data.size() == v.size() * sizeof(T)); EXPECT(::memcmp(data, v.data(), data.size()) == 0); } SECTION("copy") { using T = double; std::vector v{1, 2, 3, 4, 5, 6, 7, 8}; codec::Encoder encoder; { std::vector scoped = v; encoder = codec::Encoder(codec::copy(scoped)); scoped.assign(scoped.size(), 0); // zero out before destruction } // We can now encode with scoped vector destroyed codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT(data.size() == v.size() * sizeof(T)); EXPECT(::memcmp(data, v.data(), data.size()) == 0); } } // ------------------------------------------------------------------------------------------------------- CASE("Encoder for std::array") { SECTION("ref") { using T = double; std::array v{1, 2, 3, 4, 5, 6, 7, 8}; codec::Encoder encoder(codec::ref(v)); // We can only encode with reference to original vector (no copies were made) codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT(data.size() == v.size() * sizeof(T)); EXPECT(::memcmp(data, v.data(), data.size()) == 0); } SECTION("copy") { using T = double; std::array v{1, 2, 3, 4, 5, 6, 7, 8}; codec::Encoder encoder; { std::array scoped = v; encoder = codec::Encoder(codec::copy(scoped)); std::fill(std::begin(scoped), std::end(scoped), 0); // zero out before destruction } // We can now encode with scoped vector destroyed codec::Metadata metadata; codec::Data data; encode(encoder, metadata, data); EXPECT(data.size() == v.size() * sizeof(T)); EXPECT(::memcmp(data, v.data(), data.size()) == 0); } } // ------------------------------------------------------------------------------------------------------- CASE("Encoder of encoder") { using T = double; std::vector v{1, 2, 3, 4, 5, 6, 7, 8}; codec::Encoder encoder(codec::ref(v)); codec::Encoder encoder_of_encoder(codec::ref(encoder)); codec::Metadata metadata; codec::Data data; encode(encoder_of_encoder, metadata, data); EXPECT(data.size() == v.size() * sizeof(T)); EXPECT(::memcmp(data, v.data(), data.size()) == 0); } // ------------------------------------------------------------------------------------------------------- /// Helper class to be used in testing decoding of arrays. template struct EncodedArray { codec::Data data; codec::Metadata metadata; EncodedArray() : in{1, 2, 3, 4, 5, 6, 7, 8} { encode(in, metadata, data); } friend bool operator==(const std::vector& lhs, const EncodedArray& rhs) { if (lhs.size() != rhs.in.size()) { return false; } return ::memcmp(lhs.data(), rhs.in.data(), rhs.in.size() * sizeof(T)) == 0; } friend bool operator==(const std::array& lhs, const EncodedArray& rhs) { if (lhs.size() != rhs.in.size()) { return false; } return ::memcmp(lhs.data(), rhs.in.data(), rhs.in.size() * sizeof(T)) == 0; } private: std::vector in; }; template <> struct EncodedArray { using T = std::byte; codec::Data data; codec::Metadata metadata; EncodedArray() { std::bitset<8> bits; in.resize(5); size_t n{0}; for (auto& byte : in) { bits.set(n++, true); byte = *reinterpret_cast(&bits); } encode(in, metadata, data); } friend bool operator==(const std::vector& lhs, const EncodedArray& rhs) { if (lhs.size() != rhs.in.size()) { return false; } return ::memcmp(lhs.data(), rhs.in.data(), rhs.in.size() * sizeof(T)) == 0; } private: std::vector in; }; // ------------------------------------------------------------------------------------------------------- CASE("Decoding to std::vector") { using T = double; EncodedArray encoded; std::vector out; SECTION("decode std::vector directly") { EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, out)); EXPECT(out == encoded); } SECTION("decode using rvalue codec::Decoder (type erasure)") { EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(out))); EXPECT(out == encoded); } SECTION("decode using lvalue codec::Decoder (type erasure)") { codec::Decoder decoder(out); EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(out))); EXPECT(out == encoded); } SECTION("decode using decoder of decoder") { codec::Decoder decoder(out); EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(decoder))); EXPECT(out == encoded); } } // ------------------------------------------------------------------------------------------------------- CASE("Decoding to std::array") { using T = double; EncodedArray encoded; std::array out; SECTION("decode std::vector directly") { EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, out)); EXPECT(out == encoded); } SECTION("decode using rvalue codec::Decoder (type erasure)") { EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(out))); EXPECT(out == encoded); } SECTION("decode using lvalue codec::Decoder (type erasure)") { codec::Decoder decoder(out); EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(out))); EXPECT(out == encoded); } SECTION("decode using decoder of decoder") { codec::Decoder decoder(out); EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(decoder))); EXPECT(out == encoded); } } // ------------------------------------------------------------------------------------------------------- CASE("Encode/Decode byte array") { using T = std::byte; EncodedArray encoded; std::vector out; auto validate = [&]() { EXPECT(out == encoded); auto to_byte = [](const char* str) { return std::byte(std::bitset<8>(str).to_ulong()); }; EXPECT(out[0] == to_byte("00000001")); EXPECT(out[1] == to_byte("00000011")); EXPECT(out[2] == to_byte("00000111")); EXPECT(out[3] == to_byte("00001111")); EXPECT(out[4] == to_byte("00011111")); }; SECTION("decode directly") { EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, out)); validate(); } SECTION("decode using rvalue codec::Decoder (type erasure)") { EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(out))); validate(); } SECTION("decode using lvalue codec::Decoder (type erasure)") { codec::Decoder decoder(out); EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(out))); validate(); } SECTION("decode using decoder of decoder") { codec::Decoder decoder(out); EXPECT_NO_THROW(decode(encoded.metadata, encoded.data, codec::Decoder(decoder))); validate(); } } // ------------------------------------------------------------------------------------------------------- CASE("Encode/Decode string") { std::string in{"short string"}; codec::Metadata metadata; codec::Data data; encode(in, metadata, data); EXPECT(data.size() == 0); std::string out; decode(metadata, data, out); EXPECT(out == in); } // ------------------------------------------------------------------------------------------------------- template void test_encode_decode_scalar() { T in{std::numeric_limits::max()}; T out; codec::Metadata metadata; codec::Data data; encode(in, metadata, data); EXPECT(data.size() == 0); decode(metadata, data, out); EXPECT(out == in); } CASE("Encode/Decode scalar") { // bit identical encoding via Base64 string within the metadata! SECTION("int32") { test_encode_decode_scalar(); } SECTION("int64") { test_encode_decode_scalar(); } SECTION("real32") { test_encode_decode_scalar(); } SECTION("real64") { test_encode_decode_scalar(); } SECTION("uint64") { test_encode_decode_scalar(); } } // ------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/codec/CMakeLists.txt0000664000175000017500000000201415253173651017763 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_add_test( TARGET eckit_test_codec_encoding SOURCES test_codec_encoding.cc LIBS eckit_codec ) ecbuild_add_test( TARGET eckit_test_codec_stream SOURCES test_codec_stream.cc LIBS eckit_codec ) ecbuild_add_executable( TARGET eckit_test_codec_record SOURCES test_codec_record.cc LIBS eckit_codec NOINSTALL ) function( add_codec_test algorithm ) string( TOUPPER ${algorithm} feature ) if( eckit_HAVE_${feature} OR algorithm MATCHES "none" ) ecbuild_add_test( TARGET eckit_test_codec_record_COMPRESSION_${algorithm} COMMAND eckit_test_codec_record ARGS --suffix ".${algorithm}" ENVIRONMENT ECKIT_CODEC_COMPRESSION=${algorithm} ) endif() endfunction() add_codec_test(none) add_codec_test(aec) add_codec_test(bzip2) add_codec_test(lz4) add_codec_test(snappy) eckit-2.3.0/tests/codec/test_codec_stream.cc0000664000175000017500000001471015253173651021227 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/codec/FileStream.h" #include "eckit/codec/Session.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/FileHandle.h" #include "eckit/io/PooledHandle.h" #include "eckit/testing/Test.h" namespace eckit::test { CASE("Stream interoperability with eckit::DataHandle") { SECTION("own pointer") { codec::Stream s; { eckit::DataHandle* datahandle = new eckit::FileHandle("test_io_session.data"); datahandle->openForWrite(0); s = codec::Stream{datahandle}; } s.datahandle().close(); } SECTION("shared pointer") { codec::Stream s; { std::shared_ptr datahandle = std::make_shared("test_io_session.data"); datahandle->openForWrite(0); s = codec::Stream{datahandle}; } s.datahandle().close(); } SECTION("reference") { codec::Stream s; eckit::FileHandle datahandle("test_io_session.data"); datahandle.openForWrite(0); s = codec::Stream{datahandle}; s.datahandle().close(); } } CASE("Test seek-for-write works when opening OutputFileStream for append") { std::string s1("write \n"); std::string s2("append \n"); std::string s3("overwrite\n"); { codec::Stream f = codec::OutputFileStream("append-test"); f.write(s1.c_str(), s1.size()); } { codec::Stream f = codec::OutputFileStream("append-test", codec::Mode::append); auto offset = f.position(); f.write(s2.c_str(), s2.size()); // Rewind to beginning of append f.seek(offset); f.write(s3.c_str(), s3.size()); } { codec::Stream f = codec::InputFileStream("append-test"); std::string expected = s1 + s3; std::string read(expected.size(), ' '); f.read(const_cast(read.data()), read.size()); EXPECT_EQUAL(read, expected); } } CASE("Opening same file in same scope") { // Opening same file within same scope will avoid opening it multiple times, good for perfmance // write a file { codec::OutputFileStream out("test_io_session.data"); out.write("line1\n", 6); out.write("line2\n", 6); out.write("line3\n", 6); } std::string l1(5, ' '); std::string l2(5, ' '); std::string l3(5, ' '); codec::Stream f1 = codec::InputFileStream{"test_io_session.data"}; f1.seek(0 * 6); f1.read(const_cast(l1.data()), 5); codec::Stream f2 = codec::InputFileStream{"test_io_session.data"}; f2.seek(1 * 6); f2.read(const_cast(l2.data()), 5); codec::Stream f3 = codec::InputFileStream{"test_io_session.data"}; f3.seek(2 * 6); f3.read(const_cast(l3.data()), 5); EXPECT_EQUAL(l1, "line1"); EXPECT_EQUAL(l2, "line2"); EXPECT_EQUAL(l3, "line3"); auto& pooled_handle = dynamic_cast(f1.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 3); EXPECT(pooled_handle.nbReads() == 3); } CASE("Opening same file in parallel scopes") { // Files are opened and closed within each scope, bad for performance // write a file { codec::OutputFileStream out("test_io_session.data"); out.write("line1\n", 6); out.write("line2\n", 6); out.write("line3\n", 6); } std::string l1(5, ' '); std::string l2(5, ' '); std::string l3(5, ' '); { codec::Stream f1 = codec::InputFileStream{"test_io_session.data"}; f1.seek(0 * 6); f1.read(const_cast(l1.data()), 5); auto& pooled_handle = dynamic_cast(f1.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 1); EXPECT(pooled_handle.nbReads() == 1); } { codec::Stream f2 = codec::InputFileStream{"test_io_session.data"}; f2.seek(1 * 6); f2.read(const_cast(l2.data()), 5); auto& pooled_handle = dynamic_cast(f2.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 1); EXPECT(pooled_handle.nbReads() == 1); } { codec::Stream f3 = codec::InputFileStream{"test_io_session.data"}; f3.seek(2 * 6); f3.read(const_cast(l3.data()), 5); auto& pooled_handle = dynamic_cast(f3.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 1); EXPECT(pooled_handle.nbReads() == 1); } } CASE("Opening same file in parallel scopes with Session") { // Declaring this in an outer scope will keep storage of InputFileStream // within nested scopes, so that files will not be opened/closed repeatedly codec::Session session; // write a file { codec::OutputFileStream out("test_io_session.data"); out.write("line1\n", 6); out.write("line2\n", 6); out.write("line3\n", 6); } std::string l1(5, ' '); std::string l2(5, ' '); std::string l3(5, ' '); { codec::Stream f1 = codec::InputFileStream{"test_io_session.data"}; f1.seek(0 * 6); f1.read(const_cast(l1.data()), 5); auto& pooled_handle = dynamic_cast(f1.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 1); EXPECT(pooled_handle.nbReads() == 1); } { codec::Stream f2 = codec::InputFileStream{"test_io_session.data"}; f2.seek(1 * 6); f2.read(const_cast(l2.data()), 5); auto& pooled_handle = dynamic_cast(f2.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 2); EXPECT(pooled_handle.nbReads() == 2); } { codec::Stream f3 = codec::InputFileStream{"test_io_session.data"}; f3.seek(2 * 6); f3.read(const_cast(l3.data()), 5); auto& pooled_handle = dynamic_cast(f3.datahandle()); EXPECT(pooled_handle.nbOpens() == 1); EXPECT(pooled_handle.nbSeeks() == 3); EXPECT(pooled_handle.nbReads() == 3); } } } // namespace eckit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/testing/0000775000175000017500000000000015253173651015626 5ustar alastairalastaireckit-2.3.0/tests/testing/CMakeLists.txt0000664000175000017500000000037615253173651020374 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_add_test( TARGET eckit_test_testing SOURCES test_testing.cc LIBS eckit ) eckit-2.3.0/tests/testing/test_testing.cc0000664000175000017500000002254315253173651020657 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/types/FloatCompare.h" #include "eckit/value/Value.h" #define ECKIT_TESTING_SELF_REGISTER_CASES 0 #include "eckit/testing/Test.h" // Disable warnings for old-style casts in these tests. They are intentional #ifdef __clang__ #pragma clang diagnostic ignored "-Wold-style-cast" #endif using namespace eckit::testing; namespace eckit_test { // Not eckit namespace on purpose to test downstream usage of macros using Tests = std::vector; //---------------------------------------------------------------------------------------------------------------------- // These unit tests test the eckit testing framework itself. //---------------------------------------------------------------------------------------------------------------------- // We need some global counters to keep count of some of the CASE functionality std::vector global_counters(5, 0); void ThrowStd() { throw std::exception(); } Tests tests = {{CASE("CASE with no EXPECT passes"){ }}, {CASE("EXPECT macros are defined correctly"){EXPECT(true); EXPECT_NOT(false); EXPECT_EQUAL(1, 1); EXPECT_NOT_EQUAL(1, 2); EXPECT_NO_THROW({ bool b = true; }); EXPECT_THROWS(throw std::exception()); EXPECT_THROWS_AS(throw std::exception(), std::exception); EXPECT_MSG(1 == 1, [=]() { std::cerr << eckit::Colour::red << "1 != 1" << eckit::Colour::reset << std::endl; };); } // namespace eckit_test } // namespace eckit , {CASE("EXPECT does not cause the test to fail on success"){ Test pass = {CASE("P"){EXPECT(true); } } ; std::vector f; bool ret = pass.run(TestVerbosity::Silent, f); if (!ret || f.size() != 0) { throw eckit::testing::TestException("Unexpected error encountered in EXPECT", Here()); } } } , {CASE("EXPECT causes an error to be reported on failure"){ Test fail = {CASE("F"){EXPECT(false); } } ; std::vector f; bool ret = fail.run(TestVerbosity::Silent, f); if (ret || f.size() == 0) { throw eckit::testing::TestException("No error reported when EXPECT failed", Here()); } if (ret || f.size() > 1) { throw eckit::testing::TestException("Too many errors reported when EXPECT failed", Here()); } } } , {CASE("EXPECT succeeds for success (true) and failure (false)"){Tests pass = {{CASE("P"){EXPECT(true); } } } ; Tests fail = {{CASE("F"){EXPECT(false); } } } ; EXPECT(0 == run(pass, TestVerbosity::Silent)); EXPECT(1 == run(fail, TestVerbosity::Silent)); } } , {CASE("EXPECT succeeds for integer comparison"){EXPECT(7 == 7); EXPECT(7 != 8); EXPECT(7 >= 6); EXPECT(7 <= 8); EXPECT(7 > 6); EXPECT(7 < 8); EXPECT_NOT(7 == 8); EXPECT_NOT(7 != 7); EXPECT_NOT(7 <= 6); EXPECT_NOT(7 >= 8); EXPECT_NOT(7 < 6); EXPECT_NOT(7 > 8); } } , {CASE("Expect succeeds for integer vs. real comparison"){EXPECT(7.0 == 7); EXPECT(7.0 != 8); EXPECT(7 == 7.0); EXPECT(7 != 8.0); EXPECT_NOT(7.0 == 8); EXPECT_NOT(7 != 7.0); } } , {CASE("Expect succeeds for string comparison"){ std::string a("a"); std::string b("b"); EXPECT(a == a); EXPECT(a != b); EXPECT(b >= a); EXPECT(a <= b); EXPECT(b > a); EXPECT(a < b); EXPECT_NOT(a == b); EXPECT_NOT(a != a); EXPECT_NOT(b <= a); EXPECT_NOT(a >= b); EXPECT_NOT(b < a); EXPECT_NOT(a > b); } } , {CASE("Expect expression RHS can use * / % + -"){EXPECT(7 == 1 * 7); EXPECT(7 == 7 / 1); EXPECT(0 == 7 % 1); EXPECT(7 == 1 + 6); EXPECT(7 == 8 - 1); } } , {CASE("Expect expression LHS can use * / % + -"){EXPECT(1 * 7 == 7); EXPECT(7 / 1 == 7); EXPECT(7 % 1 == 0); EXPECT(1 + 6 == 7); EXPECT(8 - 1 == 7); } } , {CASE("run() returns the correct failure count"){Tests pass = {{CASE("P"){EXPECT(1 == 1); } } } ; Tests fail_1 = {{CASE("F1"){EXPECT(0 == 1); } } } ; Tests fail_3 = {{CASE("F1"){EXPECT(0 == 1); } } , {CASE("F2"){EXPECT(0 == 1); } } , { CASE("F3") { EXPECT(0 == 1); } } } ; EXPECT(0 == run(pass, TestVerbosity::Silent)); EXPECT(1 == run(fail_1, TestVerbosity::Silent)); EXPECT(3 == run(fail_3, TestVerbosity::Silent)); } } , {CASE("run() returns -1 if no CASE() is found"){Tests empty = {}; EXPECT(-1 == run(empty, TestVerbosity::Silent)); } } , {CASE("A fresh fixture is created for each section"){ int i = 7; SECTION("S1") { i = 42; } SECTION("S2") { EXPECT(i == 7); } } } , {CASE("Deprecated SETUP macro may be used."){ SETUP("Context"){int i = 7; SECTION("S1") { i = 42; } SECTION("S2") { EXPECT(i == 7); } } } } , {CASE("Fixture setup runs one more time than the number of sections."){ ++global_counters[0]; SECTION("S1") { ++global_counters[1]; } SECTION("S2") { ++global_counters[2]; } } } , {CASE("Sections run correctly inside a for loop"){ ++global_counters[3]; for (int j = 0; j < 10; j++) { std::stringstream ss; ss << "test-" << j; SECTION(ss.str()) { ++global_counters[4]; } } } } , {CASE("Expect runs multiple times in for-loop"){ int i = 0; for (int j = 0; j < 10; j++) { EXPECT(i++ == j); } EXPECT(i == 10); } } , {CASE("Collections compare correctly"){ SETUP("Create Collections"){std::vector a = {1, 2, 3}; std::vector b = {1, 2, 3}; SECTION("Compare Collections") { EXPECT(a == b); EXPECT(a <= b); EXPECT(a >= b); EXPECT_NOT(a != b); EXPECT_NOT(a > b); EXPECT_NOT(a < b); a[0] = 0; EXPECT_NOT(a == b); EXPECT_NOT(a >= b); EXPECT(a <= b); EXPECT(a < b); EXPECT(a != b); a[0] = 2; EXPECT_NOT(a == b); EXPECT_NOT(a <= b); EXPECT(a >= b); EXPECT(a > b); EXPECT(a != b); } } } } , {CASE("Test comparisons of c-style arrays"){ std::vector a = {1, 2, 3}; std::vector b = {1, 2, 3}; int arr[3] = {1, 2, 3}; std::vector s = {"1", "22", "333"}; std::string arr_s[3] = {"1", "22", "333"}; std::vector d1 = {1., 2., 3.}; std::vector d2 = {1., 2., 3.}; // Check basic comparisons and initializations EXPECT(make_view(a.data(), a.data() + 3) == make_view(b.data(), b.data() + 3)); EXPECT(make_view(a.data(), 3) == make_view(b.data(), 3)); EXPECT(make_view(a.data(), a.data() + 3) == make_view(arr, arr + 3)); EXPECT(make_view(a.data(), 3) == make_view(arr, 3)); EXPECT(make_view(a) == make_view(arr, arr + 3)); EXPECT(make_view(s) == make_view(arr_s, arr_s + 3)); EXPECT(make_view(s.data(), s.data() + 3) == make_view(arr_s, arr_s + 3)); EXPECT(make_view(a) == make_view(b.data(), b.data() + 3)); EXPECT(make_view(a) == make_view(b)); EXPECT(make_view(d1.data(), 3) == make_view(d2.data(), 3)); EXPECT(make_view(d1.data(), 3) == make_view(d2.data(), 3)); EXPECT(make_view(a.data(), a.data() + 3).size() == 3); EXPECT(make_view(a.data(), 3).size() == 3); EXPECT(make_view(a).size() == a.size()); EXPECT(make_view(s).size() == s.size()); // Check sub-array comparisons EXPECT(make_view(a.data(), a.data() + 2) == make_view(b.data(), b.data() + 2)); EXPECT(make_view(&a[1], &a[1] + 2) == make_view(&b[1], &b[1] + 2)); EXPECT(make_view(&a[1], &a[1] + 2) != make_view(b.data(), b.data() + 2)); // values not equal EXPECT(make_view(a.data(), a.data() + 3) != make_view(b.data(), b.data() + 2)); // not same size EXPECT(make_view(a.data(), 2) == make_view(b.data(), 2)); EXPECT(make_view(a.data(), 2) == make_view(b.data(), 2)); // Check != is the same as !(==) EXPECT(!(make_view(a.data(), a.data() + 3) != make_view(b.data(), b.data() + 3))); EXPECT(!(make_view(a.data(), a.data() + 3) == make_view(b.data(), b.data() + 2))); // not same size EXPECT(!(make_view(&a[1], &a[1] + 2) == make_view(b.data(), b.data() + 2))); // values not equal // Check comparisons against std::vector EXPECT(a == make_view(b)); EXPECT(make_view(a) == b); // Check type recognition of make_view vs explicit constructor EXPECT(ArrayView(a) == make_view(a)); EXPECT(ArrayView(s) == make_view(s)); // Check approximately equals d2[2] = 2.99; EXPECT(is_approximately_equal(make_view(d1), make_view(d2), 0.02)); EXPECT(!is_approximately_equal(make_view(d1), make_view(d2), 0.0001)); // array vs array EXPECT(!is_approximately_equal(d1, d2, 0.0001)); // vector vs vector EXPECT(!is_approximately_equal(make_view(d1), d2, 0.0001)); // array vs vector EXPECT(!is_approximately_equal(d1, make_view(d2), 0.0001)); // vector vs array } } , } ; // end of tests //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit using eckit_test::global_counters; int main(int argc, char* argv[]) { int retval1 = 0; if (global_counters.size() != 5 || std::any_of(global_counters.begin(), global_counters.end(), [](int a) { return a != 0; })) { eckit::Log::info() << "Global counters incorrectly configured" << std::endl; retval1 = 1; } eckit::Main::initialise(argc, argv); int retval2 = eckit::testing::run_tests(eckit_test::tests, argc, argv); int retval3 = 0; if (global_counters[0] != 3 || global_counters[1] != 1 || global_counters[2] != 1 || global_counters[3] != 11 || global_counters[4] != 10) { eckit::Log::info() << "Global counters incorrect. Relevant tests FAILED" << std::endl; eckit::Log::info() << "Global counters: " << global_counters << std::endl; retval3 = 1; } return std::abs(retval1) + std::abs(retval2) + std::abs(retval3); } eckit-2.3.0/tests/serialisation/0000775000175000017500000000000015253173651017017 5ustar alastairalastaireckit-2.3.0/tests/serialisation/CMakeLists.txt0000664000175000017500000000064515253173651021564 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_add_test( TARGET eckit_test_serialisation_file_stream SOURCES test_file_stream.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_serialisation_streamable SOURCES test_streamable.cc LIBS eckit ) eckit-2.3.0/tests/serialisation/test_streamable.cc0000664000175000017500000002434315253173651022512 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 /// @file test_streamable.cc /// @date Jan 2015 /// @author Florian Rathgeber #include #include #include #include "eckit/filesystem/PathName.h" #include "eckit/io/AutoCloser.h" #include "eckit/serialisation/FileStream.h" #include "eckit/serialisation/Streamable.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- template class TestItem : public Streamable { public: TestItem(const T& s) : payload_(s) {} TestItem(Stream& s) : Streamable(s), payload_() { s >> payload_; } // From Streamble virtual void encode(eckit::Stream& s) const { Streamable::encode(s); s << payload_; } virtual const eckit::ReanimatorBase& reanimator() const; // Class members static const eckit::ClassSpec& classSpec() { return classSpec_; } T payload_; protected: virtual void print(std::ostream& s) const { s << "TestItem " << payload_; } private: // -- Class members static eckit::ClassSpec classSpec_; // -- Friends friend std::ostream& operator<<(std::ostream& s, const TestItem& p) { p.print(s); return s; } }; template const ReanimatorBase& TestItem::reanimator() const { static Reanimator > reanimator; return reanimator; } using uchar = unsigned char; using llong = long long; #ifndef ulong using ulong = unsigned long; #endif using ullong = unsigned long long; /// Partially specialise ClassSpecs since they must have unique names template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemChar", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemUChar", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemBool", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemInt", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemUInt", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemShort", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemUShort", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemLong", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemULong", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemLLong", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemULLong", }; // NOTE: float is not implemented! // template <> // ClassSpec TestItem::classSpec_ = {&Streamable::classSpec(),"TestItemFloat",}; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemDouble", }; template <> ClassSpec TestItem::classSpec_ = { &Streamable::classSpec(), "TestItemString", }; //---------------------------------------------------------------------------------------------------------------------- #define test_decode(TYPE, INITIAL, SUFFIX) \ CASE("test_decode_" #TYPE "_" #SUFFIX) { \ Log::info() << "Manually (de)serialise Streamable with " #TYPE " member" << std::endl; \ PathName filename = PathName::unique("data"); \ std::string filepath = filename.asString(); \ TestItem t(INITIAL); \ { \ FileStream sout(filepath.c_str(), "w"); \ auto c = closer(sout); \ t.encode(sout); \ } \ { \ FileStream sin(filepath.c_str(), "r"); \ auto c = closer(sin); \ TestItem t2(sin); \ Log::info() << "original: " << t.payload_ << std::endl; \ Log::info() << "streamed: " << t2.payload_ << std::endl; \ EXPECT(t.payload_ == t2.payload_); \ } \ if (filename.exists()) \ filename.unlink(); \ } #define test_reanimate(TYPE, INITIAL, SUFFIX) \ CASE("test_reanimate_" #TYPE "_" #SUFFIX) { \ Log::info() << "(de)serialise Streamable with " #TYPE " member via Reanimator" << std::endl; \ PathName filename = PathName::unique("data"); \ std::string filepath = filename.asString(); \ TestItem t(INITIAL); \ { \ FileStream sout(filepath.c_str(), "w"); \ auto c = closer(sout); \ sout << t; \ } \ { \ FileStream sin(filepath.c_str(), "r"); \ auto c = closer(sin); \ TestItem* t2 = eckit::Reanimator >::reanimate(sin); \ Log::info() << "orginal: " << t.payload_ << std::endl; \ Log::info() << "streamed: " << t2->payload_ << std::endl; \ EXPECT(t.payload_ == t2->payload_); \ delete t2; \ } \ if (filename.exists()) \ filename.unlink(); \ } test_decode(char, 'A', max); test_decode(char, 'z', min); test_reanimate(char, 'A', max); test_reanimate(char, 'z', min); test_decode(uchar, 'A', max); test_reanimate(uchar, 'A', max); test_decode(bool, true, true); test_reanimate(bool, true, true); test_decode(bool, false, false); test_reanimate(bool, false, false); test_decode(int, numeric_limits::max(), max); test_decode(int, numeric_limits::min(), min); test_reanimate(int, numeric_limits::max(), max); test_reanimate(int, numeric_limits::min(), min); test_decode(uint, numeric_limits::max(), max); test_reanimate(uint, numeric_limits::max(), max); test_decode(short, numeric_limits::max(), max); test_decode(short, numeric_limits::min(), min); test_reanimate(short, numeric_limits::max(), max); test_reanimate(short, numeric_limits::min(), min); test_decode(ushort, numeric_limits::max(), max); test_reanimate(ushort, numeric_limits::max(), max); // NOTE: long in eckit is always 32 bit! test_decode(long, numeric_limits::max(), max); test_decode(long, numeric_limits::min(), min); test_reanimate(long, numeric_limits::max(), max); test_reanimate(long, numeric_limits::min(), min); test_decode(ulong, numeric_limits::max(), max); test_reanimate(ulong, numeric_limits::max(), max); test_decode(llong, numeric_limits::max(), max); test_decode(llong, numeric_limits::min(), min); test_reanimate(llong, numeric_limits::max(), max); test_reanimate(llong, numeric_limits::min(), min); test_decode(ullong, numeric_limits::max(), max); test_reanimate(ullong, numeric_limits::max(), max); // NOTE: float is not implemented! // test_decode(float, numeric_limits::max(), max); // test_decode(float, numeric_limits::min(), min); // test_reanimate(float, numeric_limits::max(), max); // test_reanimate(float, numeric_limits::min(), min); test_decode(double, numeric_limits::max(), max); test_decode(double, numeric_limits::min(), min); test_reanimate(double, numeric_limits::max(), max); test_reanimate(double, numeric_limits::min(), min); test_decode(string, "Hello, World!", Hello); test_reanimate(string, "Hello, World!", Hello); test_decode(string, "", empty); test_reanimate(string, "", empty); //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/serialisation/test_file_stream.cc0000664000175000017500000000773515253173651022673 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/io/AutoCloser.h" #include "eckit/serialisation/FileStream.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- namespace { const char i_char('a'); const unsigned char i_uchar('b'); const bool i_bool(true); const int i_int(-20000000); const unsigned int i_uint(30000000); const short i_short(-4000); const unsigned short i_ushort(5000); const long i_long(-60000000); const unsigned long i_ulong(70000000); const long long i_longlong(-80000000); const unsigned long long i_ulonglong(90000000); // const float i_float(300000); const double i_double(100000000); const string i_string("abcdefghijklmnopqrstuvwx"); const char* i_charp("cdefghijklmnopqrstuvwxyz"); char v_char; unsigned char v_uchar; bool v_bool; int v_int; unsigned int v_uint; short v_short; unsigned short v_ushort; long v_long; unsigned long v_ulong; long long v_longlong; unsigned long long v_ulonglong; // float v_float; double v_double; string v_string; string v_charp; } // anonymous namespace struct F { ~F() { if (filename.exists()) { filename.unlink(); } } static PathName filename; }; PathName F::filename = PathName::unique("data"); //---------------------------------------------------------------------------------------------------------------------- CASE("write_data") { FileStream sout(F::filename, "w"); auto c = closer(sout); sout << i_char << i_uchar << i_bool << i_int << i_uint << i_short << i_ushort << i_long << i_ulong << i_longlong << i_ulonglong // << i_float << i_double << i_string << i_charp; } CASE("read_data") { FileStream sin(F::filename, "r"); auto c = closer(sin); sin >> v_char >> v_uchar >> v_bool >> v_int >> v_uint >> v_short >> v_ushort >> v_long >> v_ulong >> v_longlong >> v_ulonglong // >> v_float >> v_double >> v_string >> v_charp; } CASE("check_data") { EXPECT(v_char == i_char); EXPECT(v_uchar == i_uchar); EXPECT(v_bool == i_bool); EXPECT(v_int == i_int); EXPECT(v_uint == i_uint); EXPECT(v_short == i_short); EXPECT(v_ushort == i_ushort); EXPECT(v_long == i_long); EXPECT(v_ulong == i_ulong); EXPECT(v_longlong == i_longlong); EXPECT(v_ulonglong == i_ulonglong); // EXPECT( v_float == i_float ); EXPECT(v_double == i_double); EXPECT(v_string == i_string); EXPECT(v_charp == i_charp); } CASE("stream_object") { Log::info() << "Stream an object" << std::endl; const std::string k("key"); const std::string v("value"); { FileStream sout(F::filename, "w"); auto c = closer(sout); sout.startObject(); sout << k; sout << v; sout.endObject(); } { FileStream sin(F::filename, "r"); auto c = closer(sin); EXPECT(sin.next()); std::string s; sin >> s; EXPECT(s == k); sin >> s; EXPECT(s == v); EXPECT(sin.endObjectFound()); EXPECT(!sin.next()); } } CASE("stream_string") { Log::info() << "Stream a string" << std::endl; { FileStream sout(F::filename, "w"); auto c = closer(sout); sout << i_string; } { FileStream sin(F::filename, "r"); auto c = closer(sin); std::string s; EXPECT(sin.next(s)); EXPECT(s == i_string); EXPECT(!sin.next()); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/large_file/0000775000175000017500000000000015253173651016242 5ustar alastairalastaireckit-2.3.0/tests/large_file/CMakeLists.txt0000664000175000017500000000041515253173651021002 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_add_test( TARGET eckit_test_large_file SOURCES test_large_file.cc LIBS eckit ) eckit-2.3.0/tests/large_file/test_large_file.cc0000664000175000017500000000513315253173651021703 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include #include #include #include #include "eckit/eckit.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "eckit/log/Bytes.h" #include "eckit/testing/Test.h" #define TOTAL_SIZE (SIZE_2G + SIZE_1M) // 2Gb + 1Mb file // #define TOTAL_SIZE 2*SIZE_2G // 4Gb file #define SIZE_2G 2147483648 // 2^31 #define SIZE_1M 1048576 // 2^20 using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_large_file") { std::cout << "off_t can index " << Bytes(std::pow(2.0, (int)sizeof(off_t) * 8)) << std::endl; // make temporary file name int fd; char filename[] = "large.XXXXXX"; if ((fd = ::mkstemp(filename)) == -1) { ::perror("cannot create temporary file"), ::exit(EXIT_FAILURE); } std::cout << "openning file '" << filename << "'" << std::endl; // file is opened in mkstemp, so this is not needed // if( ( fd = ::open(filename, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600) ) == -1 ) // perror("Error opening file"), exit(EXIT_FAILURE); // stretch the file size if ((long long)::lseek(fd, TOTAL_SIZE - 1, SEEK_SET) < 0) { ::perror("Error calling lseek() to 'stretch' the file"), ::exit(EXIT_FAILURE); } // write something to the end of the file to actually resize it correctly if (::write(fd, "", 1) != 1) { ::close(fd), perror("Error writing last byte of the file"), ::exit(EXIT_FAILURE); } // lock the region beyond the 2GB limit until the EOF struct flock fl; fl.l_type = F_WRLCK; // F_RDLCK, F_WRLCK, F_UNLCK fl.l_whence = SEEK_SET; // SEEK_SET, SEEK_CUR, SEEK_END fl.l_start = SIZE_2G; // Offset from l_whence fl.l_len = 0; // length, 0 = to EOF fl.l_pid = getpid(); // PID fcntl(fd, F_SETLKW, &fl); // unlock the region fl.l_type = F_UNLCK; fcntl(fd, F_SETLK, &fl); eckit::PathName file(filename); file.unlink(); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/spec/0000775000175000017500000000000015253173651015103 5ustar alastairalastaireckit-2.3.0/tests/spec/CMakeLists.txt0000664000175000017500000000044515253173651017646 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 foreach(_test custom layered) ecbuild_add_test( TARGET eckit_test_spec_${_test} SOURCES ${_test}.cc LIBS eckit_spec ) endforeach() eckit-2.3.0/tests/spec/custom.cc0000664000175000017500000003074715253173651016737 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include "eckit/geo/Exceptions.h" #include "eckit/parser/YAMLParser.h" #include "eckit/spec/Custom.h" #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h" namespace eckit::spec::test { template struct is_vector : std::false_type {}; template struct is_vector> : std::true_type {}; template constexpr bool is_vector_v = is_vector::value; template void test_t() { T a; T b; T c; if constexpr (std::is_same_v>) { a = b = {"1", "2", "3"}; c = {"7", "8", "9", "10"}; } else if constexpr (std::is_same_v>) { a = b = {false, false, true}; c = {true, false, true}; } else if constexpr (std::is_same_v) { a = b = "1"; c = "7"; } else if constexpr (is_vector_v) { a = b = {1, 2, 3}; c = {7, 8, 9, 10}; } else { a = b = 1; c = 7; } EXPECT_NOT(a != b); EXPECT(a == b); EXPECT_NOT(a < b); EXPECT(a <= b); EXPECT_NOT(a > b); EXPECT(a >= b); EXPECT(a != c); EXPECT_NOT(a == c); EXPECT(a < c); EXPECT(a <= c); EXPECT_NOT(a > c); EXPECT_NOT(a >= c); } CASE("Custom::value_type") { test_t(); // test_t(); test_t(); test_t(); test_t(); test_t(); test_t(); test_t(); test_t>(); test_t>(); test_t>(); test_t>(); test_t>(); test_t>(); test_t>(); test_t>(); } CASE("Custom::container_type") { Custom spec({{std::string{"foo"}, "bar"}}); std::string bar; EXPECT(spec.get("foo", bar) && bar == "bar"); } CASE("Custom::custom_type") { Custom custom1({{"custom1", Custom::custom_ptr(new Custom({{"foo", "bar"}, {"boo", "far"}}))}}); EXPECT(custom1.str() == R"({"custom1":{"boo":"far","foo":"bar"}})"); Custom custom2({{"custom2", Custom::custom_ptr(new Custom(custom1.container()))}}); EXPECT(custom2.str() == R"({"custom2":{"custom1":{"boo":"far","foo":"bar"}}})"); Custom customer1({{"customer", Custom::custom_ptr(Custom::make_from_value( YAMLParser::decodeString("{name: John Smith, age: 33}")))}}); EXPECT(customer1.str() == R"({"customer":{"age":33,"name":"John Smith"}})"); Custom customer2({{"customer", Custom::custom_ptr(Custom::make_from_value(YAMLParser::decodeString(R"( name: John Smith age: 33 )")))}}); EXPECT(customer2.str() == customer1.str()); Custom nested({{"a", Custom::custom_ptr(Custom::make_from_value(YAMLParser::decodeString(R"( b: c: 1 d: "2" )")))}}); EXPECT(nested.str() == R"({"a":{"b":{"c":1,"d":"2"}}})"); EXPECT(nested.has_custom("a")); EXPECT_NOT(nested.has_custom("a?")); EXPECT_THROWS_AS(nested.custom("a?"), exception::SpecError); EXPECT(nested.custom("a")->has_custom("b")); EXPECT_NOT(nested.custom("a")->has_custom("b?")); EXPECT_THROWS_AS(nested.custom("a")->custom("b?"), exception::SpecError); const auto& b = nested.custom("a")->custom("b"); ASSERT(b); int c = 0; std::string d; EXPECT(b->get("c", c) && c == 1); EXPECT(b->get("d", d) && d == "2"); } CASE("Custom::operator==") { Custom a({{"foo", "bar"}, {"boo", "far"}}); Custom b({{"bOo", "far"}, {"Foo", "bar"}}); Custom c({{"foo", "bar"}, {"boo", "faR"}}); EXPECT(a == b); EXPECT(a != c); EXPECT_NOT(a != b); EXPECT_NOT(a == c); // not commutative Custom d({{"foo", "bar"}, {"boo", "far"}, {"roo", "fab"}}); EXPECT(a == d); EXPECT(d != a); EXPECT_NOT(a != d); EXPECT_NOT(d == a); Custom e({{"mask", std::vector{true, false, true}}}); Custom f({{"mask", std::vector{true, false, true}}}); Custom g({{"mask", std::vector{false, true, false}}}); EXPECT(e == f); EXPECT(e != g); } CASE("Spec <- Custom") { constexpr int zero = 0; constexpr int one = 1; constexpr double two = 2.; const std::string three = "3"; SECTION("access") { std::unique_ptr spec(new Custom({{"a", -123}, {"b", "B"}, {"c", 123UL}})); int a = 0; EXPECT(spec->get("a", a)); EXPECT_EQUAL(a, -123); std::string b; EXPECT(spec->get("b", b)); EXPECT_EQUAL(b, "B"); size_t c = 0; EXPECT(spec->get("c", c)); EXPECT_EQUAL(c, 123UL); std::string b2; EXPECT(spec->get("B", b2)); EXPECT_EQUAL(b2, b); int d = 0; dynamic_cast(spec.get())->set("B", 321); EXPECT(spec->get("b", d)); EXPECT_EQUAL(d, 321); } SECTION("conversion (1)") { Custom a({ {"double", static_cast(one)}, {"float", static_cast(one)}, {"int", static_cast(one)}, {"long", static_cast(one)}, {"size_t", static_cast(one)}, }); // test scalar type conversion for (const auto& [key, value] : a.container()) { double value_as_double = 0; float value_as_float = 0; EXPECT(a.get(key, value_as_double) && value_as_double == static_cast(one)); EXPECT(a.get(key, value_as_float) && value_as_float == static_cast(one)); if (key == "int" || key == "long" || key == "size_t") { int value_as_int = 0; long value_as_long = 0; size_t value_as_size_t = 0; EXPECT(a.get(key, value_as_int) && value_as_int == static_cast(one)); EXPECT(a.get(key, value_as_long) && value_as_long == static_cast(one)); EXPECT(a.get(key, value_as_size_t) && value_as_size_t == static_cast(one)); EXPECT(a.get_string(key) == std::to_string(1)); } else { EXPECT(a.get_string(key) == std::to_string(1.)); } } } SECTION("conversion (2)") { Custom b({ {"true", true}, {"false", false}, {"zero", 0}, {"one", 1}, }); EXPECT(b.get_bool("true")); EXPECT(!b.get_bool("false")); bool maybe = false; EXPECT(!b.has("?")); EXPECT(!b.get_bool("?", false)); EXPECT(b.get_bool("?", true)); EXPECT(b.get("true", maybe = false) && maybe); EXPECT(b.get_bool("true", true)); EXPECT(b.get_bool("true", false)); EXPECT(b.get("false", maybe = true) && !maybe); EXPECT(!b.get_bool("false", true)); EXPECT(!b.get_bool("false", false)); EXPECT(!b.get_bool("zero")); EXPECT(!b.get_bool("zero", maybe = true)); EXPECT(b.get("zero", maybe = true) && !maybe); EXPECT(b.get_bool("one")); EXPECT(b.get_bool("one", maybe = false)); EXPECT(b.get("one", maybe = false) && maybe); } SECTION("conversion (3)") { Custom c; EXPECT_NOT(c.has("foo")); c.set("foo", two); EXPECT(c.has("foo")); EXPECT_THROWS_AS(c.get_int("foo"), exception::SpecError); // cannot access as int EXPECT(types::is_approximately_equal(c.get_double("foo"), two)); EXPECT(c.get_string("foo") == std::to_string(two)); c.set("bar", one); EXPECT(c.get_int("bar") == one); EXPECT(types::is_approximately_equal(c.get_double("bar"), static_cast(one))); EXPECT(c.get_string("bar") == "1"); c.set("foo", three); EXPECT(c.get_string("foo") == three); Custom d(c.container()); EXPECT(d.has("foo")); EXPECT(d.get_string("foo") == three); EXPECT_THROWS_AS(d.get_int("foo"), exception::SpecError); // cannot access as int EXPECT_THROWS_AS(d.get_double("foo"), exception::SpecError); // cannot access as real d.set("foo", one); EXPECT(d.get_int("foo") == one); Custom e(d.container()); ASSERT(e.has("foo")); ASSERT(e.has("bar")); } SECTION("conversion (4)") { Custom e({{"zero", zero}, {"one", one}, {"two", two}}); bool maybe = true; EXPECT(!e.get("?", maybe) && maybe); // non-existant key EXPECT(!e.get("two", maybe) && maybe); // non-convertible EXPECT(e.get("zero", maybe) && !maybe); EXPECT(e.get("one", maybe) && maybe); } SECTION("conversion (5)") { std::unique_ptr custom(Custom::make_from_value(YAMLParser::decodeString( "{bool_list: [true, false, true], long_list: [1, 2, 3], double_list: [1, 2.1, 3]}"))); const std::vector expected_bool_list{true, false, true}; std::vector bool_list; EXPECT(custom->get("bool_list", bool_list)); EXPECT(bool_list == expected_bool_list); const std::vector expected_long_list{1, 2, 3}; auto long_list = custom->get_double_vector("long_list"); EXPECT(long_list.size() == expected_long_list.size()); EXPECT(std::equal(long_list.begin(), long_list.end(), expected_long_list.begin())); const std::vector expected_double_list{1, 2.1, 3}; auto double_list = custom->get_double_vector("double_list"); EXPECT(double_list.size() == expected_double_list.size()); EXPECT(std::equal(double_list.begin(), double_list.end(), expected_double_list.begin(), [](const auto& a, const auto& b) { return types::is_approximately_equal(a, b); })); } SECTION("conversion (6)") { const std::vector expected_bool_list{true, false, true}; Custom custom; custom.set("bool_list", expected_bool_list); const Spec& spec = custom; std::vector bool_list; EXPECT(spec.get("bool_list", bool_list)); EXPECT(bool_list == expected_bool_list); std::vector int_list; EXPECT(!spec.get("bool_list", int_list)); } SECTION("json") { // test order std::unique_ptr a(new Custom({{"c", "c"}, {"a", "a"}, {"b", 1}})); const std::string a_str = a->str(); const std::string a_ref = R"({"a":"a","b":1,"c":"c"})"; EXPECT_EQUAL(a_str, a_ref); // test types std::unique_ptr b(new Custom({{"string", "string"}, {"bool", true}, {"int", static_cast(1)}, {"long", static_cast(2)}, {"long long", static_cast(3)}, {"size_t", static_cast(4)}, {"float", static_cast(5)}, {"double", static_cast(6)}, {"vector", std::vector{true, false}}, {"vector", std::vector{1, 1}}, {"vector", std::vector{2, 2}}, {"vector", std::vector{3, 3}}, {"vector", std::vector{4, 4}}, {"vector", std::vector{5, 5}}, {"vector", std::vector{6, 6}}, {"vector", std::vector{"string", "string"}}})); const std::string b_str = b->str(); const std::string b_ref = R"({"bool":true,"double":6,"float":5,"int":1,"long":2,"long long":3,"size_t":4,"string":"string","vector":[true,false],"vector":[6,6],"vector":[5,5],"vector":[1,1],"vector":[3,3],"vector":[2,2],"vector":[4,4],"vector":["string","string"]})"; EXPECT_EQUAL(b_str, b_ref); } } } // namespace eckit::spec::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/spec/layered.cc0000664000175000017500000000233615253173651017043 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/spec/Layered.h" #include "eckit/geo/Exceptions.h" #include "eckit/spec/Custom.h" #include "eckit/testing/Test.h" namespace eckit::spec::test { CASE("Spec <- Layered") { int one = 1; double two = 2.; std::vector expected_bool_list = {true, false, true}; spec::Custom a({{"foo", one}, {"bar", two}, {"bool_list", expected_bool_list}}); ASSERT(a.has("foo")); ASSERT(a.has("bar")); ASSERT(a.has("bool_list")); spec::Layered b(a); ASSERT(b.has("foo")); ASSERT(b.has("bar")); b.hide("foo"); EXPECT_NOT(b.has("foo")); b.unhide("foo"); ASSERT(b.has("foo")); EXPECT(a.get_int("foo") == one); auto value = b.get_int("foo"); EXPECT(value == one); std::vector bool_list; EXPECT(b.get("bool_list", bool_list)); EXPECT(bool_list == expected_bool_list); b.hide("bool_list"); EXPECT_NOT(b.get("bool_list", bool_list)); } } // namespace eckit::spec::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/thread/0000775000175000017500000000000015253173651015420 5ustar alastairalastaireckit-2.3.0/tests/thread/CMakeLists.txt0000664000175000017500000000105415253173651020160 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 if( NOT ENABLE_WARNINGS ) list( APPEND suppress_warnings $<$:-h nomessage=3140> # Disable optimisation warning in debug build due to use of intrinsics ) set_source_files_properties( test_mutex.cc PROPERTIES COMPILE_OPTIONS "${suppress_warnings}" ) endif() ecbuild_add_test( TARGET eckit_test_thread_mutex SOURCES test_mutex.cc LIBS eckit ) eckit-2.3.0/tests/thread/test_mutex.cc0000664000175000017500000000242315253173651020131 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/thread/Mutex.h" #include "eckit/thread/Thread.h" #include "eckit/thread/ThreadControler.h" #include "eckit/testing/Test.h" using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- class Locker : public eckit::Thread { public: Locker(Mutex* m, bool& v) : m_(m), v_(v) {} Mutex* m_; bool& v_; void run() { v_ = m_->tryLock(); } }; CASE("Mutex Lock/Unlock") { Mutex* m; EXPECT_NO_THROW(m = new Mutex()); bool gotLock = false; EXPECT_NO_THROW(gotLock = m->tryLock()); EXPECT(gotLock); EXPECT_NO_THROW(m->unlock()); EXPECT_NO_THROW(m->lock()); ThreadControler thread(new Locker(m, gotLock), false); thread.start(); EXPECT_NO_THROW(thread.wait()); EXPECT(!gotLock); EXPECT_NO_THROW(m->unlock()); delete m; } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/0000775000175000017500000000000015253173651015315 5ustar alastairalastaireckit-2.3.0/tests/types/test_hour.cc0000664000175000017500000000532415253173651017644 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h" #include "eckit/types/Hour.h" using namespace std; using namespace eckit; using namespace eckit::testing; using eckit::types::is_approximately_equal; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Basic test") { EXPECT(Hour(12) == Hour("12")); EXPECT(Hour(12.5) == Hour("12:30")); EXPECT(Hour(1.0 / 3.0) == Hour("0:20")); EXPECT(Hour(2.0 / 3.0) == Hour("0:40")); EXPECT(Hour(1.0 / 60.0) == Hour("0:01")); EXPECT(Hour(1.0 / 3600.0) == Hour("0:00:01")); } CASE("Test minutes") { for (int hour = 0; hour < 240; ++hour) { for (int minute = 0; minute < 60; ++minute) { std::ostringstream oss; if (minute == 0) { oss << hour; } else { oss << hour << ':' << std::setw(2) << std::setfill('0') << minute; } std::string s = oss.str(); // std::cout << s << std::endl; // std::cout << Hour(s) << std::endl; EXPECT(Hour(s).asString() == s); double d = double(hour) + double(minute) / 60.0; EXPECT(Hour(d).asString() == s); } } } CASE("Test seconds") { for (int hour = 0; hour < 24; ++hour) { for (int minute = 0; minute < 60; ++minute) { for (int second = 0; second < 60; ++second) { std::ostringstream oss; if (second == 0) { if (minute == 0) { oss << hour; } else { oss << hour << ':' << std::setw(2) << std::setfill('0') << minute; } } else { oss << hour << ':' << std::setw(2) << std::setfill('0') << minute << ':' << std::setw(2) << std::setfill('0') << second; } std::string s = oss.str(); // std::cout << s << std::endl; // std::cout << Hour(s) << std::endl; EXPECT(Hour(s).asString() == s); double d = double(hour) + double(minute) / 60.0 + double(second) / 3600.0; EXPECT(Hour(d).asString() == s); } } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/CMakeLists.txt0000664000175000017500000000344615253173651020064 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 option( ENABLE_ECKIT-395 "Control if ECKIT-395 tests should be enabled" ON ) # eckit_test_types_fixedstring known to fail compilation with PGI 19.4 (working with PGI 18.10) ecbuild_add_test( TARGET eckit_test_types_cache SOURCES test_cache.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_types_doublecompare SOURCES test_doublecompare.cc LIBS eckit_maths ) ecbuild_add_test( TARGET eckit_test_types_floatcompare SOURCES test_floatcompare.cc LIBS eckit_maths ) ecbuild_add_test( TARGET eckit_test_types_time SOURCES test_time.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_types_uuid SOURCES test_uuid.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_types_print_vector SOURCES test_print_vector.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_types_fixedstring SOURCES test_fixedstring.cc LIBS eckit CONDITION ENABLE_ECKIT-395 ) ecbuild_add_test( TARGET eckit_test_types_fraction SOURCES test_fraction.cc LIBS eckit ) ecbuild_add_test( TARGET eckit_test_types_hour SOURCES test_hour.cc LIBS eckit ) # performance test ecbuild_add_test( TARGET eckit_test_types_double_compare_speed SOURCES test-double-compare-speed.cc LIBS eckit ) eckit-2.3.0/tests/types/test_floatcompare.cc0000664000175000017500000002571415253173651021350 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include "eckit/maths/FloatingPointExceptions.h" #include "eckit/types/FloatCompare.h" #include "eckit/testing/Test.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- namespace { bool is_equal(float a, float b, float epsilon, int maxUlps) { return types::is_approximately_equal(a, b, epsilon, maxUlps); } bool is_equal(float a, float b, float epsilon) { return types::is_approximately_equal(a, b, epsilon); } bool is_equal(float a, float b) { return types::is_approximately_equal(a, b, 0.00001f); } const float dEps = std::numeric_limits::epsilon(); const float dInf = std::numeric_limits::infinity(); const float sMin = std::numeric_limits::denorm_min(); const float dMin = std::numeric_limits::min(); const float dMax = std::numeric_limits::max(); const float qNaN = std::numeric_limits::quiet_NaN(); const float sNaN = std::numeric_limits::signaling_NaN(); }; // namespace //---------------------------------------------------------------------------------------------------------------------- CASE("test_large_numbers") { Log::info() << "test_large_numbers" << std::endl; EXPECT(is_equal(1000000, 1000000)); EXPECT(is_equal(1000000, 1000000.00001)); EXPECT(is_equal(1000000.00001, 1000000)); EXPECT(!is_equal(1000000.0, 1000001.0)); EXPECT(!is_equal(1000001.0, 1000000.0)); // ----------------------------------------------- EXPECT(is_equal(dMax, dMax)); EXPECT(is_equal(dMax, dMax, dEps)); EXPECT(is_equal(dMin, dMin)); EXPECT(is_equal(dMin, dMin, dEps)); } CASE("test_negative_large_numbers") { Log::info() << "test_negative_large_numbers " << dMin << std::endl; EXPECT(is_equal(-1000000, -1000000)); EXPECT(is_equal(-1000000, -1000000.00001)); EXPECT(is_equal(-1000000.00001, -1000000)); EXPECT(!is_equal(-1000000.0, -1000001.0)); EXPECT(!is_equal(-1000001.0, -1000000.0)); // ----------------------------------------------- EXPECT(is_equal(-dMax, -dMax)); EXPECT(is_equal(-dMax, -dMax, dEps)); EXPECT(is_equal(-dMin, -dMin)); EXPECT(is_equal(-dMin, -dMin, dEps)); } CASE("test_large_numbers_of_opposite_sign") { EXPECT(!is_equal(-1000000, 1000000)); EXPECT(!is_equal(-1000000, 1000000.00001)); EXPECT(!is_equal(-1000000.00001, 1000000)); EXPECT(!is_equal(-1000000.0, 1000001.0)); EXPECT(!is_equal(-1000001.0, 1000000.0)); // Overflow can occur here (as in CRAY) in eckit::types::is_approximately_equal maths::FloatingPointExceptions::disable_floating_point_exceptions(); EXPECT(!is_equal(-dMax, dMax)); EXPECT(!is_equal(-dMax, dMax, dEps)); maths::FloatingPointExceptions::enable_floating_point_exceptions(); } CASE("test_ulp_around_one") { Log::info() << "test_ulp_around_one" << std::endl; // ULP distances up to 10 are equal // Going right from 1 by eps increases distance by 1 // Going left from 1 by eps increases distance by 2 for (int i = 0; i <= 10; ++i) { EXPECT(is_equal(1.0 + i * dEps, 1.0, dEps)); EXPECT(is_equal(1.0, 1.0 + i * dEps, dEps)); EXPECT(is_equal(1.0 - i * dEps / 2, 1.0, dEps)); EXPECT(is_equal(1.0, 1.0 - i * dEps / 2, dEps)); } // ULP distances greater 10 are not equal EXPECT(!is_equal(1.0 + 11 * dEps, 1.0, dEps)); EXPECT(!is_equal(1.0, 1.0 + 11 * dEps, dEps)); EXPECT(!is_equal(1.0 - 11 * dEps / 2, 1.0, dEps)); EXPECT(!is_equal(1.0, 1.0 - 11 * dEps / 2, dEps)); } CASE("test_numbers_around_one") { Log::info() << "test_numbers_around_one" << std::endl; EXPECT(is_equal(1.0000001, 1.0000002)); EXPECT(is_equal(1.0000002, 1.0000001)); EXPECT(is_equal(1.123456, 1.123457)); EXPECT(is_equal(1.12345, 1.12344, 0.001)); EXPECT(!is_equal(1.0001, 1.0002)); EXPECT(!is_equal(1.0002, 1.0001)); } CASE("test_numbers_around_negative_one") { Log::info() << "test_numbers_around_negative_one" << std::endl; EXPECT(is_equal(-1.0000001, -1.0000002)); EXPECT(is_equal(-1.0000002, -1.0000001)); EXPECT(!is_equal(-1.0001, -1.0002)); EXPECT(!is_equal(-1.0002, -1.0001)); } CASE("test_numbers_between_one_and_zero") { Log::info() << "test_numbers_between_one_and_zero" << std::endl; EXPECT(is_equal(0.000000001000001, 0.000000001000002)); EXPECT(is_equal(0.000000001000002, 0.000000001000001)); EXPECT(!is_equal(0.0012, 0.0011)); EXPECT(!is_equal(0.0011, 0.0012)); } CASE("test_numbers_between_minusone_and_zero") { Log::info() << "test_numbers_between_minusone_and_zero" << std::endl; EXPECT(is_equal(-0.000000001000001, -0.000000001000002)); EXPECT(is_equal(-0.000000001000002, -0.000000001000001)); EXPECT(!is_equal(-0.0012, -0.0011)); EXPECT(!is_equal(-0.0011, -0.0012)); } CASE("test_comparisons_involving_zero") { Log::info() << "test_comparisons_involving_zero" << std::endl; EXPECT(is_equal(0.0, 0.0)); EXPECT(is_equal(0.0, -0.0)); EXPECT(is_equal(-0.0, -0.0)); EXPECT(!is_equal(0.0001, 0.0)); EXPECT(!is_equal(0.0, 0.0001)); EXPECT(!is_equal(-0.0001, 0.0)); EXPECT(!is_equal(0.0, -0.0001)); EXPECT(is_equal(0.0, 1e-30, 0.01)); EXPECT(is_equal(1e-30, 0.0, 0.01)); EXPECT(!is_equal(1e-30, 0.0, 1e-31)); EXPECT(!is_equal(0.0, 1e-30, 1e-31)); EXPECT(is_equal(0.0, -1e-30, 0.1)); EXPECT(is_equal(-1e-30, 0.0, 0.1)); EXPECT(!is_equal(-1e-30, 0.0, 1e-31)); EXPECT(!is_equal(0.0, -1e-30, 1e-31)); } CASE("test_comparisons_involving_infinity") { Log::info() << "test_comparisons_involving_infinity" << std::endl; if (std::numeric_limits::has_infinity) { EXPECT(is_equal(dInf, dInf)); EXPECT(is_equal(-dInf, -dInf)); EXPECT(!is_equal(dInf, dMax)); EXPECT(!is_equal(dMax, dInf)); EXPECT(!is_equal(-dInf, -dMax)); EXPECT(!is_equal(-dMax, -dInf)); } else { Log::info() << "test_comparisons_involving_infinity NOT VALID on this platform" << std::endl; } } CASE("test_comparisons_involving_nan") { Log::info() << "test_comparisons_involving_nan" << std::endl; // The value NaN (Not a Number) is used to represent a value that does not represent a real number. // NaN's are represented by a bit pattern with an exponent of all 1s and a non-zero fraction. T // there are two categories of NaN: QNaN (Quiet NaN) and SNaN (Signalling NaN). // // A QNaN is a NaN with the most significant fraction bit set. // QNaN's propagate freely through most arithmetic operations. // These values pop out of an operation when the result is not mathematically defined. // An SNaN is a NaN with the most significant fraction bit clear. // It is used to signal an exception when used in operations. // SNaN's can be handy to assign to uninitialized variables to trap premature usage. // Semantically, QNaN's denote indeterminate operations, while SNaN's denote invalid operations. EXPECT(!is_equal(qNaN, qNaN)); EXPECT(!is_equal(qNaN, 0.0)); EXPECT(!is_equal(-0.0, qNaN)); EXPECT(!is_equal(qNaN, -0.0)); EXPECT(!is_equal(0.0, qNaN)); EXPECT(!is_equal(qNaN, dInf)); EXPECT(!is_equal(dInf, qNaN)); EXPECT(!is_equal(qNaN, dMax)); EXPECT(!is_equal(dMax, qNaN)); EXPECT(!is_equal(qNaN, -dMax)); EXPECT(!is_equal(-dMax, qNaN)); EXPECT(!is_equal(qNaN, dMin)); EXPECT(!is_equal(dMin, qNaN)); EXPECT(!is_equal(qNaN, -dMin)); EXPECT(!is_equal(-dMin, qNaN)); maths::FloatingPointExceptions::disable_floating_point_exceptions(); EXPECT(!is_equal(sNaN, sNaN)); EXPECT(!is_equal(sNaN, 0.0)); EXPECT(!is_equal(-0.0, sNaN)); EXPECT(!is_equal(sNaN, -0.0)); EXPECT(!is_equal(0.0, sNaN)); EXPECT(!is_equal(sNaN, dInf)); EXPECT(!is_equal(dInf, sNaN)); EXPECT(!is_equal(sNaN, dMax)); EXPECT(!is_equal(dMax, sNaN)); EXPECT(!is_equal(sNaN, -dMax)); EXPECT(!is_equal(-dMax, sNaN)); EXPECT(!is_equal(sNaN, dMin)); EXPECT(!is_equal(dMin, sNaN)); EXPECT(!is_equal(sNaN, -dMin)); EXPECT(!is_equal(-dMin, sNaN)); maths::FloatingPointExceptions::enable_floating_point_exceptions(); } CASE("test_comparisons_opposite_side_of_zero") { Log::info() << "test_comparisons_opposite_side_of_zero" << std::endl; EXPECT(!is_equal(1.0000001, -1.0)); EXPECT(!is_equal(-1.0, 1.0000001)); EXPECT(!is_equal(-1.0000001, 1.0)); EXPECT(!is_equal(1.0, -1.0000001)); EXPECT(is_equal(10.0 * dMin, 10.0 * -dMin)); EXPECT(is_equal(10000 * dMin, 10000 * -dMin)); } CASE("test_comparisons_very_close_to_zero") { Log::info() << "test_comparisons_very_close_to_zero" << std::endl; EXPECT(is_equal(dMin, -dMin, dEps)); EXPECT(is_equal(-dMin, dMin, dEps)); EXPECT(is_equal(dMin, 0, dEps)); EXPECT(is_equal(0, dMin, dEps)); EXPECT(is_equal(-dMin, 0, dEps)); EXPECT(is_equal(0, -dMin, dEps)); EXPECT(is_equal(0.000000001, -dMin)); EXPECT(is_equal(0.000000001, dMin)); EXPECT(is_equal(dMin, 0.000000001)); EXPECT(is_equal(-dMin, 0.000000001)); EXPECT(!is_equal(0.000000001, -dMin, 1e-10)); EXPECT(!is_equal(0.000000001, dMin, 1e-10)); EXPECT(!is_equal(dMin, 0.000000001, 1e-10)); EXPECT(!is_equal(-dMin, 0.000000001, 1e-10)); } CASE("test_comparisons_with_denormal_numbers") { Log::info() << "test_comparisons_with_denormal_numbers" << std::endl; EXPECT(is_equal(sMin, -sMin, dEps)); EXPECT(is_equal(-sMin, sMin, dEps)); EXPECT(is_equal(sMin, 0, dEps)); EXPECT(is_equal(0, sMin, dEps)); EXPECT(is_equal(-sMin, 0, dEps)); EXPECT(is_equal(0, -sMin, dEps)); const float lMin = dMin - sMin; // largest denormal number EXPECT(is_equal(lMin, -lMin, dEps)); EXPECT(is_equal(-lMin, lMin, dEps)); EXPECT(is_equal(lMin, 0, dEps)); EXPECT(is_equal(0, lMin, dEps)); EXPECT(is_equal(-lMin, 0, dEps)); EXPECT(is_equal(0, -lMin, dEps)); } CASE("test_comparisons_ulps") { Log::info() << "test_comparisons_ulps" << std::endl; EXPECT(is_equal(dMin, -dMin, 0, 2)); EXPECT(is_equal(-dMin, dMin, 0, 2)); EXPECT(is_equal(dMin, 0, 0, 1)); EXPECT(is_equal(0, dMin, 0, 1)); EXPECT(is_equal(-dMin, 0, 0, 1)); EXPECT(is_equal(0, -dMin, 0, 1)); EXPECT(!is_equal(dMin, -dMin, 0, 1)); EXPECT(!is_equal(-dMin, dMin, 0, 1)); EXPECT(!is_equal(dMin, 0, 0, 0)); EXPECT(!is_equal(0, dMin, 0, 0)); EXPECT(!is_equal(-dMin, 0, 0, 0)); EXPECT(!is_equal(0, -dMin, 0, 0)); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { eckit::maths::FloatingPointExceptions::enable_floating_point_exceptions(); return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/types/test_time.cc0000664000175000017500000001331715253173651017626 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/testing/Test.h" #include "eckit/types/Time.h" using namespace std; using namespace eckit; using namespace eckit::testing; using eckit::types::is_approximately_equal; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Time only digits (hhmmss)") { EXPECT(Time(0, 0, 0) == Time("0")); EXPECT(Time(0, 0, 0) == Time("00")); EXPECT(Time(0, 0, 0) == Time("000")); EXPECT(Time(0, 0, 0) == Time("0000")); EXPECT(Time(0, 0, 0) == Time("00000")); EXPECT(Time(0, 0, 0) == Time("000000")); EXPECT(Time(0, 0, 0) == Time(0)); EXPECT(Time(0, 0, 1) == Time(1)); EXPECT(Time(0, 1, 0) == Time(60)); EXPECT(Time(0, 1, 1) == Time(61)); EXPECT(Time(1, 0, 0) == Time(3600)); EXPECT(Time(1, 0, 1) == Time(3601)); EXPECT(Time(1, 1, 0) == Time(3660)); EXPECT(Time(1, 1, 1) == Time(3661)); EXPECT(Time(2, 3, 4) == Time(3600 * 2 + 60 * 3 + 4)); EXPECT_THROWS(Time(24 * 3600)); EXPECT_THROWS(Time(24, 0, 0)); EXPECT_THROWS(Time("24")); EXPECT_THROWS(Time(-1)); EXPECT_THROWS(Time("-1")); EXPECT_NO_THROW(Time(-1, 0, 0, true)); EXPECT_NO_THROW(Time("-1", true)); EXPECT(Time(-1, 0, 0, true) == Time("-1", true)); EXPECT(Time(24, 0, 0, true) == Time(24 * 3600, true)); EXPECT(Time(24, 0, 0, true) == Time("24", true)); EXPECT(Time(-1, 0, 0, true) == Time("-1", true)); EXPECT(Time(-1, 0, 0, true) == Time("-01", true)); EXPECT(Time(-100, 0, 0, true) == Time("-100", true)); EXPECT(Time(-100, 0, 0, true) == Time("-0100", true)); EXPECT(Time(0, -30, 0, true) == Time("-0.5", true)); EXPECT(Time(2, 0, 0) == Time("2")); EXPECT(Time(2, 0, 0) == Time("02")); EXPECT(Time(2, 0, 0) == Time("200")); EXPECT(Time(2, 0, 0) == Time("0200")); EXPECT(Time(2, 0, 0) == Time("20000")); EXPECT(Time(2, 0, 0) == Time("020000")); EXPECT(Time(20, 0, 0) == Time("20")); EXPECT(Time(20, 0, 0) == Time("2000")); EXPECT(Time(20, 0, 0) == Time("200000")); EXPECT(Time(20, 0, 0) == Time("20", true)); EXPECT_THROWS(Time(30, 0, 0)); EXPECT_THROWS(Time("30")); EXPECT_THROWS(Time("3000")); EXPECT_THROWS(Time("300000")); EXPECT(Time(30, 0, 0, true) == Time("30", true)); EXPECT(Time(0, 3, 0) == Time("003")); EXPECT(Time(0, 3, 0) == Time("0003")); EXPECT(Time(0, 3, 0) == Time("00300")); EXPECT(Time(0, 3, 0) == Time("000300")); EXPECT(Time(0, 0, 4) == Time("00004")); EXPECT(Time(0, 0, 4) == Time("000004")); EXPECT(Time(1, 23, 0) == Time("123")); EXPECT(Time(1, 23, 0) == Time("0123")); EXPECT(Time(1, 23, 0) == Time("12300")); EXPECT(Time(1, 23, 0) == Time("012300")); EXPECT(Time(1, 23, 45) == Time("12345")); EXPECT(Time(1, 23, 45) == Time("012345")); EXPECT_THROWS(Time("25")); EXPECT_THROWS(Time("175")); EXPECT_THROWS(Time("0175")); EXPECT_THROWS(Time("3025")); EXPECT_THROWS(Time("017345")); EXPECT_THROWS(Time("012375")); } CASE("Time format (hh:mm:ss)") { EXPECT(Time(0, 0, 0) == Time("0:0")); EXPECT(Time(0, 0, 0) == Time("0:00")); EXPECT(Time(0, 0, 0) == Time("00:0")); EXPECT(Time(0, 0, 0) == Time("00:00")); EXPECT(Time(0, 0, 0) == Time("0:00:00")); EXPECT(Time(0, 0, 0) == Time("00:0:00")); EXPECT(Time(0, 0, 0) == Time("00:00:0")); EXPECT(Time(0, 0, 0) == Time("00:00:00")); EXPECT(Time(2, 0, 0) == Time("2:0")); EXPECT(Time(2, 0, 0) == Time("02:0")); EXPECT(Time(2, 0, 0) == Time("2:00")); EXPECT(Time(2, 0, 0) == Time("02:00")); EXPECT(Time(2, 0, 0) == Time("2:00:00")); EXPECT(Time(2, 0, 0) == Time("02:00:00")); EXPECT(Time(0, 3, 0) == Time("00:3")); EXPECT(Time(0, 3, 0) == Time("00:03")); EXPECT(Time(0, 3, 0) == Time("0:03:00")); EXPECT(Time(0, 3, 0) == Time("00:3:00")); EXPECT(Time(0, 3, 0) == Time("00:03:00")); EXPECT(Time(0, 0, 4) == Time("00:00:4")); EXPECT(Time(0, 0, 4) == Time("00:00:04")); EXPECT(Time(1, 23, 0) == Time("1:23")); EXPECT(Time(1, 23, 0) == Time("01:23")); EXPECT(Time(1, 23, 0) == Time("1:23:00")); EXPECT(Time(1, 23, 0) == Time("01:23:00")); EXPECT(Time(1, 23, 45) == Time("1:23:45")); EXPECT(Time(1, 23, 45) == Time("01:23:45")); EXPECT_THROWS(Time("25")); EXPECT_THROWS(Time("175")); EXPECT_THROWS(Time("0175")); EXPECT_THROWS(Time("3025")); EXPECT_THROWS(Time("017345")); EXPECT_THROWS(Time("012375")); } CASE("Time with unit (__h__m__s)") { EXPECT(Time(2, 0, 0) == Time("2h")); EXPECT(Time(2, 0, 0) == Time("0002H")); EXPECT(Time(2, 0, 0) == Time("120m")); EXPECT(Time(2, 0, 0) == Time("7200s")); EXPECT(Time(0, 3, 0) == Time("3M")); EXPECT(Time(0, 3, 0) == Time("180s")); EXPECT(Time(1, 2, 3) == Time("1h2m3s")); EXPECT(Time(1, 23, 45) == Time("1h23m45s")); EXPECT(Time(1, 23, 45) == Time("01h23m45s")); EXPECT(Time(1, 23, 45) == Time("5025s")); EXPECT(Time(1, 23, 45) == Time("83m45s")); EXPECT_THROWS(Time("25h")); EXPECT_NO_THROW(Time("25h", true)); EXPECT(Time(0, -30, 0, true) == Time("-30m", true)); EXPECT(Time(-1, -30, 0, true) == Time("-1h30m", true)); EXPECT(Time(0, -90, 0, true) == Time("-1h30m", true)); EXPECT(Time("0d3h10m20s") == Time("3h10m20s")); EXPECT(Time("0d3h10m20s") == Time("3h620s")); EXPECT(Time("2D3h", true) == Time("51h", true)); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char* argv[]) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/test_fraction.cc0000664000175000017500000002233615253173651020476 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/log/Log.h" #include "eckit/types/Fraction.h" #include "eckit/utils/Translator.h" #include "eckit/testing/Test.h" namespace eckit::test { auto& LOG = Log::info(); //---------------------------------------------------------------------------------------------------------------------- CASE("Constructing fractions") { // 0 EXPECT(Fraction(0, 1) == Fraction()); EXPECT_THROWS_AS(Fraction(0, 0), std::exception); // this prints a backtrace wi Assertion failed // negative number EXPECT(Fraction(-1, 2) == Fraction(3, -6)); // decimals EXPECT(Fraction(0.16) == Fraction(16, 100)); EXPECT(Fraction(0.1616) == Fraction(1616, 10000)); // 5 / 7 EXPECT(Fraction(0.714285) != Fraction(5, 7)); EXPECT(Fraction(0.7142857142) != Fraction(5, 7)); EXPECT(Fraction(0.71428571428) == Fraction(5, 7)); EXPECT(Fraction(0.714285714285) == Fraction(5, 7)); EXPECT(Fraction(0.714285714286) == Fraction(5, 7)); EXPECT(Fraction(0.714285714285714285) == Fraction(5, 7)); // 1 / 6 EXPECT(Fraction(0.166) != Fraction(1, 6)); EXPECT(Fraction(0.1666) != Fraction(1, 6)); EXPECT(Fraction(0.16666) != Fraction(1, 6)); EXPECT(Fraction(0.166666) != Fraction(1, 6)); EXPECT(Fraction(0.1666666) != Fraction(1, 6)); EXPECT(Fraction(0.16666666) != Fraction(1, 6)); EXPECT(Fraction(0.166666666) != Fraction(1, 6)); EXPECT(Fraction(0.16666666666) == Fraction(1, 6)); EXPECT(Fraction(0.166666666666) == Fraction(1, 6)); EXPECT(Fraction(0.1666666666666) == Fraction(1, 6)); EXPECT(Fraction(0.16666666666666) == Fraction(1, 6)); EXPECT(Fraction(0.166666666666666) == Fraction(1, 6)); EXPECT(Fraction(0.1666666666666666) == Fraction(1, 6)); // 1 / 3 EXPECT(Fraction(0.3333333333) == Fraction(1, 3)); EXPECT(Fraction(0.3333333333333333) == Fraction(1, 3)); // 1 / 1 EXPECT(Fraction(0.9999999999999999) == Fraction(1, 1)); EXPECT(Fraction(0.9999999999999999) == Fraction(10, 10)); // 7 / 10.. EXPECT(Fraction(0.7) == Fraction(7, 10)); EXPECT(Fraction(0.07) == Fraction(7, 100)); EXPECT(Fraction(0.0000007) == Fraction(7, 10000000)); // operations EXPECT(Fraction(1, 3) + Fraction(2, 3) == Fraction(10, 10)); EXPECT(Fraction(1, 3) - Fraction(2, 6) == Fraction(0, 10)); EXPECT(Fraction(1, 3) * 3 == Fraction(1)); EXPECT(-Fraction(1, 3) == Fraction(1, -3)); EXPECT(2 * Fraction(1, 3) == Fraction(2, 3)); Fraction a(1, 3); Fraction b(3, 28); EXPECT(a + b == Fraction(37, 84)); EXPECT(a - b == Fraction(19, 84)); EXPECT(a * b == Fraction(1, 28)); EXPECT(a / b == Fraction(28, 9)); EXPECT(a > b); EXPECT(a != b); EXPECT_NOT(a < b); EXPECT_NOT(a == b); EXPECT(Fraction("1/3") == Fraction(1, 3)); EXPECT(double(Fraction("1/3")) == 1.0 / 3.0); EXPECT(Fraction("1") == Fraction(1)); EXPECT(Fraction("1.2") == Fraction(12, 10)); EXPECT(Fraction("1e-6") == Fraction(1, 1000000)); EXPECT(Fraction("1e+6") == Fraction(1000000, 1)); EXPECT(Fraction("1.2e+6") == Fraction(1200000, 1)); EXPECT(Fraction(M_PI) > Fraction(M_E)); EXPECT(Fraction(M_E) > Fraction(M_SQRT2)); LOG << "pi = " << (M_PI - double(Fraction(M_PI))) << std::endl; LOG << "e = " << (M_E - double(Fraction(M_E))) << std::endl; LOG << "sqrt2 = " << (M_SQRT2 - double(Fraction(M_SQRT2))) << std::endl; // EXPECT(Fraction(M_PI), Fraction(1200000, 1)); { Fraction west(-12); Fraction east(1.2); Fraction increment(1.2); Fraction f(west); while (f < east) { f += increment; } EXPECT(f == east); } { Fraction west("-77"); Fraction east("7"); Fraction increment("0.7"); Fraction f(west); while (f < east) { f += increment; } EXPECT(f == east); } EXPECT(Fraction(241.85) / Fraction(0.175) == 1382); EXPECT(Fraction(284.025) / Fraction(0.175) == 1623); // EXPECT(Fraction(5, 3).intergralPart() == 1); // EXPECT(Fraction(5, 3).decimalPart() == Fraction(1, 3)); } //---------------------------------------------------------------------------------------------------------------------- CASE("Overflow during comparisons") { { Fraction A(5934563467713522511, 13567822205000000); // 437.39985519021053 Fraction B(8624662771, 19718023); // 437.3999751902105 EXPECT(A < B); EXPECT(A <= B); EXPECT(A != B); EXPECT(B > A); EXPECT(B >= A); } { constexpr auto MAX = std::numeric_limits::max(); Fraction A(MAX - 6, MAX - 1); Fraction B(MAX - 2, MAX - 1); LOG << MAX - 6 << " ?= " << static_cast(MAX - 6) << std::endl; LOG << MAX - 2 << " ?= " << static_cast(MAX - 2) << std::endl; /// @note this demonstrates that numerators/denominators have a lossy representation in double EXPECT(double(MAX - 6) == double(MAX - 2)); EXPECT(A != B); /// @note these fail due to the lossy conversion to double of numerators/denominators // EXPECT(A < B); // EXPECT(A <= B); // EXPECT(B > A); // EXPECT(B >= A); LOG << "Max denominator" << Fraction::max_denominator() << std::endl; Fraction U(1, 1); /// @note these fail due to the lossy conversion to double of numerators/denominators EXPECT(A != U); EXPECT(A < U); EXPECT(A <= U); EXPECT(U > A); EXPECT(U >= A); EXPECT(B != U); EXPECT(B < U); EXPECT(B <= U); EXPECT(U > B); EXPECT(U >= B); } } //---------------------------------------------------------------------------------------------------------------------- CASE("Regression (Fraction <=> Fraction)") { Fraction A(-34093871309, 378680550); // -90.033331 Fraction B(-12621809, 378680550); // -0.3333102 EXPECT(A < B); EXPECT(A <= B); EXPECT(A != B); EXPECT(B > A); EXPECT(B >= A); } //---------------------------------------------------------------------------------------------------------------------- CASE("Regression (double <=> Fraction)") { double A(253.07432399999999006467987783253); Fraction B(/*253.07435716708162*/ 1591682572, 6289387); EXPECT(A < B); EXPECT(A <= B); EXPECT(A != B); EXPECT(B > A); EXPECT(B >= A); } //---------------------------------------------------------------------------------------------------------------------- CASE("Regression (Fraction <=> double)") { Fraction A(/*-253.07435716708162*/ -1591682572, 6289387); double B(-253.07432399999999006467987783253); EXPECT(A < B); EXPECT(A <= B); EXPECT(A != B); EXPECT(B > A); EXPECT(B >= A); } //---------------------------------------------------------------------------------------------------------------------- CASE("Values known to have problematic conversion to fraction") { auto values = std::vector{19.011363983154297, 0.47718059708975263}; for (auto value : values) { LOG << "Test " << value << "..." << std::endl; auto frac = Fraction(value); LOG << "Test " << value << " = " << frac << std::endl; EXPECT_NOT_EQUAL(frac.denominator(), 0); } } //---------------------------------------------------------------------------------------------------------------------- CASE("Fraction safe conversion") { constexpr auto safe = static_cast(std::numeric_limits::max()); EXPECT_NO_THROW(Fraction{safe}); constexpr auto unsafe = safe + 1; EXPECT_THROWS_AS(Fraction{unsafe}, BadCast); } //---------------------------------------------------------------------------------------------------------------------- CASE("Fraction inverse") { for (const auto& test : { std::make_pair(Fraction{1, 2}, Fraction{2, 1}), std::make_pair(Fraction{1, 3}, Fraction{3, 1}), }) { LOG << "Test (" << test.first << ")**-1 = " << test.second << std::endl; EXPECT(test.first.inverse() == test.second); LOG << "Test " << test.first << " = (" << test.second << ")**-1" << std::endl; EXPECT(test.first == test.second.inverse()); } Fraction zero(0); LOG << "Test (" << zero << ")**-1 (throw BadValue)" << std::endl; EXPECT_THROWS_AS(zero.inverse(), BadValue); } //---------------------------------------------------------------------------------------------------------------------- CASE("String to double to fraction to double to string") { Translator s2d; std::string s("-17.9229"); Fraction f(s2d(s)); std::ostringstream oss; oss << double(f); EXPECT(oss.str() == s); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { eckit::test::LOG.precision(16); return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/types/test_doublecompare.cc0000664000175000017500000002576115253173651021517 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include "eckit/maths/FloatingPointExceptions.h" #include "eckit/types/FloatCompare.h" #include "eckit/testing/Test.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- namespace { bool is_equal(double a, double b, double epsilon, int maxUlps) { return types::is_approximately_equal(a, b, epsilon, maxUlps); } bool is_equal(double a, double b, double epsilon) { return types::is_approximately_equal(a, b, epsilon); } bool is_equal(double a, double b) { return types::is_approximately_equal(a, b, 0.00001); } const double dEps = std::numeric_limits::epsilon(); const double dInf = std::numeric_limits::infinity(); const double sMin = std::numeric_limits::denorm_min(); const double dMin = std::numeric_limits::min(); const double dMax = std::numeric_limits::max(); const double qNaN = std::numeric_limits::quiet_NaN(); const double sNaN = std::numeric_limits::signaling_NaN(); }; // namespace //---------------------------------------------------------------------------------------------------------------------- CASE("test_large_numbers") { Log::info() << "test_large_numbers" << std::endl; EXPECT(is_equal(1000000, 1000000)); EXPECT(is_equal(1000000, 1000000.00001)); EXPECT(is_equal(1000000.00001, 1000000)); EXPECT(!is_equal(1000000.0, 1000001.0)); EXPECT(!is_equal(1000001.0, 1000000.0)); // ----------------------------------------------- EXPECT(is_equal(dMax, dMax)); EXPECT(is_equal(dMax, dMax, dEps)); EXPECT(is_equal(dMin, dMin)); EXPECT(is_equal(dMin, dMin, dEps)); } CASE("test_negative_large_numbers") { Log::info() << "test_negative_large_numbers " << dMin << std::endl; EXPECT(is_equal(-1000000, -1000000)); EXPECT(is_equal(-1000000, -1000000.00001)); EXPECT(is_equal(-1000000.00001, -1000000)); EXPECT(!is_equal(-1000000.0, -1000001.0)); EXPECT(!is_equal(-1000001.0, -1000000.0)); // ----------------------------------------------- EXPECT(is_equal(-dMax, -dMax)); EXPECT(is_equal(-dMax, -dMax, dEps)); EXPECT(is_equal(-dMin, -dMin)); EXPECT(is_equal(-dMin, -dMin, dEps)); } CASE("test_large_numbers_of_opposite_sign") { EXPECT(!is_equal(-1000000, 1000000)); EXPECT(!is_equal(-1000000, 1000000.00001)); EXPECT(!is_equal(-1000000.00001, 1000000)); EXPECT(!is_equal(-1000000.0, 1000001.0)); EXPECT(!is_equal(-1000001.0, 1000000.0)); // Overflow can occur here (as in CRAY) in eckit::types::is_approximately_equal maths::FloatingPointExceptions::disable_floating_point_exceptions(); EXPECT(!is_equal(-dMax, dMax)); EXPECT(!is_equal(-dMax, dMax, dEps)); maths::FloatingPointExceptions::enable_floating_point_exceptions(); } CASE("test_ulp_around_one") { Log::info() << "test_ulp_around_one" << std::endl; // ULP distances up to 10 are equal // Going right from 1 by eps increases distance by 1 // Going left from 1 by eps increases distance by 2 for (int i = 0; i <= 10; ++i) { EXPECT(is_equal(1.0 + i * dEps, 1.0, dEps)); EXPECT(is_equal(1.0, 1.0 + i * dEps, dEps)); EXPECT(is_equal(1.0 - i * dEps / 2, 1.0, dEps)); EXPECT(is_equal(1.0, 1.0 - i * dEps / 2, dEps)); } // ULP distances greater 10 are not equal EXPECT(!is_equal(1.0 + 11 * dEps, 1.0, dEps)); EXPECT(!is_equal(1.0, 1.0 + 11 * dEps, dEps)); EXPECT(!is_equal(1.0 - 11 * dEps / 2, 1.0, dEps)); EXPECT(!is_equal(1.0, 1.0 - 11 * dEps / 2, dEps)); } CASE("test_numbers_around_one") { Log::info() << "test_numbers_around_one" << std::endl; EXPECT(is_equal(1.0000001, 1.0000002)); EXPECT(is_equal(1.0000002, 1.0000001)); EXPECT(is_equal(1.12345, 1.12346)); EXPECT(is_equal(1.12345, 1.12344, 0.001)); EXPECT(!is_equal(1.0001, 1.0002)); EXPECT(!is_equal(1.0002, 1.0001)); } CASE("test_numbers_around_negative_one") { Log::info() << "test_numbers_around_negative_one" << std::endl; EXPECT(is_equal(-1.0000001, -1.0000002)); EXPECT(is_equal(-1.0000002, -1.0000001)); EXPECT(!is_equal(-1.0001, -1.0002)); EXPECT(!is_equal(-1.0002, -1.0001)); } CASE("test_numbers_between_one_and_zero") { Log::info() << "test_numbers_between_one_and_zero" << std::endl; EXPECT(is_equal(0.000000001000001, 0.000000001000002)); EXPECT(is_equal(0.000000001000002, 0.000000001000001)); EXPECT(!is_equal(0.00102, 0.00101)); EXPECT(!is_equal(0.00101, 0.00102)); } CASE("test_numbers_between_minusone_and_zero") { Log::info() << "test_numbers_between_minusone_and_zero" << std::endl; EXPECT(is_equal(-0.000000001000001, -0.000000001000002)); EXPECT(is_equal(-0.000000001000002, -0.000000001000001)); EXPECT(!is_equal(-0.00102, -0.00101)); EXPECT(!is_equal(-0.00101, -0.00102)); } CASE("test_comparisons_involving_zero") { Log::info() << "test_comparisons_involving_zero" << std::endl; EXPECT(is_equal(0.0, 0.0)); EXPECT(is_equal(0.0, -0.0)); EXPECT(is_equal(-0.0, -0.0)); EXPECT(!is_equal(0.0001, 0.0)); EXPECT(!is_equal(0.0, 0.0001)); EXPECT(!is_equal(-0.0001, 0.0)); EXPECT(!is_equal(0.0, -0.0001)); EXPECT(is_equal(0.0, 1e-40, 0.01)); EXPECT(is_equal(1e-40, 0.0, 0.01)); EXPECT(!is_equal(1e-40, 0.0, 1e-41)); EXPECT(!is_equal(0.0, 1e-40, 1e-41)); EXPECT(is_equal(0.0, -1e-40, 0.1)); EXPECT(is_equal(-1e-40, 0.0, 0.1)); EXPECT(!is_equal(-1e-40, 0.0, 1e-41)); EXPECT(!is_equal(0.0, -1e-40, 1e-41)); } CASE("test_comparisons_involving_infinity") { Log::info() << "test_comparisons_involving_infinity" << std::endl; if (std::numeric_limits::has_infinity) { EXPECT(is_equal(dInf, dInf)); EXPECT(is_equal(-dInf, -dInf)); EXPECT(!is_equal(dInf, dMax)); EXPECT(!is_equal(dMax, dInf)); EXPECT(!is_equal(-dInf, -dMax)); EXPECT(!is_equal(-dMax, -dInf)); } else { Log::info() << "test_comparisons_involving_infinity NOT VALID on this platform" << std::endl; } } CASE("test_comparisons_involving_nan") { Log::info() << "test_comparisons_involving_nan" << std::endl; // The value NaN (Not a Number) is used to represent a value that does not represent a real number. // NaN's are represented by a bit pattern with an exponent of all 1s and a non-zero fraction. T // there are two categories of NaN: QNaN (Quiet NaN) and SNaN (Signalling NaN). // // A QNaN is a NaN with the most significant fraction bit set. // QNaN's propagate freely through most arithmetic operations. // These values pop out of an operation when the result is not mathematically defined. // An SNaN is a NaN with the most significant fraction bit clear. // It is used to signal an exception when used in operations. // SNaN's can be handy to assign to uninitialized variables to trap premature usage. // Semantically, QNaN's denote indeterminate operations, while SNaN's denote invalid operations. EXPECT(!is_equal(qNaN, qNaN)); EXPECT(!is_equal(qNaN, 0.0)); EXPECT(!is_equal(-0.0, qNaN)); EXPECT(!is_equal(qNaN, -0.0)); EXPECT(!is_equal(0.0, qNaN)); EXPECT(!is_equal(qNaN, dInf)); EXPECT(!is_equal(dInf, qNaN)); EXPECT(!is_equal(qNaN, dMax)); EXPECT(!is_equal(dMax, qNaN)); EXPECT(!is_equal(qNaN, -dMax)); EXPECT(!is_equal(-dMax, qNaN)); EXPECT(!is_equal(qNaN, dMin)); EXPECT(!is_equal(dMin, qNaN)); EXPECT(!is_equal(qNaN, -dMin)); EXPECT(!is_equal(-dMin, qNaN)); maths::FloatingPointExceptions::disable_floating_point_exceptions(); EXPECT(!is_equal(sNaN, sNaN)); EXPECT(!is_equal(sNaN, 0.0)); EXPECT(!is_equal(-0.0, sNaN)); EXPECT(!is_equal(sNaN, -0.0)); EXPECT(!is_equal(0.0, sNaN)); EXPECT(!is_equal(sNaN, dInf)); EXPECT(!is_equal(dInf, sNaN)); EXPECT(!is_equal(sNaN, dMax)); EXPECT(!is_equal(dMax, sNaN)); EXPECT(!is_equal(sNaN, -dMax)); EXPECT(!is_equal(-dMax, sNaN)); EXPECT(!is_equal(sNaN, dMin)); EXPECT(!is_equal(dMin, sNaN)); EXPECT(!is_equal(sNaN, -dMin)); EXPECT(!is_equal(-dMin, sNaN)); maths::FloatingPointExceptions::enable_floating_point_exceptions(); } CASE("test_comparisons_opposite_side_of_zero") { Log::info() << "test_comparisons_opposite_side_of_zero" << std::endl; EXPECT(!is_equal(1.000000001, -1.0)); EXPECT(!is_equal(-1.0, 1.000000001)); EXPECT(!is_equal(-1.000000001, 1.0)); EXPECT(!is_equal(1.0, -1.000000001)); EXPECT(is_equal(10.0 * dMin, 10.0 * -dMin)); EXPECT(is_equal(10000 * dMin, 10000 * -dMin)); } CASE("test_comparisons_very_close_to_zero") { Log::info() << "test_comparisons_very_close_to_zero" << std::endl; EXPECT(is_equal(dMin, -dMin, dEps)); EXPECT(is_equal(-dMin, dMin, dEps)); EXPECT(is_equal(dMin, 0, dEps)); EXPECT(is_equal(0, dMin, dEps)); EXPECT(is_equal(-dMin, 0, dEps)); EXPECT(is_equal(0, -dMin, dEps)); EXPECT(is_equal(0.000000001, -dMin)); EXPECT(is_equal(0.000000001, dMin)); EXPECT(is_equal(dMin, 0.000000001)); EXPECT(is_equal(-dMin, 0.000000001)); EXPECT(!is_equal(0.000000001, -dMin, 1e-10)); EXPECT(!is_equal(0.000000001, dMin, 1e-10)); EXPECT(!is_equal(dMin, 0.000000001, 1e-10)); EXPECT(!is_equal(-dMin, 0.000000001, 1e-10)); } CASE("test_comparisons_with_denormal_numbers") { Log::info() << "test_comparisons_with_denormal_numbers" << std::endl; EXPECT(is_equal(sMin, -sMin, dEps)); EXPECT(is_equal(-sMin, sMin, dEps)); EXPECT(is_equal(sMin, 0, dEps)); EXPECT(is_equal(0, sMin, dEps)); EXPECT(is_equal(-sMin, 0, dEps)); EXPECT(is_equal(0, -sMin, dEps)); const double lMin = dMin - sMin; // largest denormal number EXPECT(is_equal(lMin, -lMin, dEps)); EXPECT(is_equal(-lMin, lMin, dEps)); EXPECT(is_equal(lMin, 0, dEps)); EXPECT(is_equal(0, lMin, dEps)); EXPECT(is_equal(-lMin, 0, dEps)); EXPECT(is_equal(0, -lMin, dEps)); } CASE("test_comparisons_ulps") { Log::info() << "test_comparisons_ulps" << std::endl; EXPECT(is_equal(dMin, -dMin, 0, 2)); EXPECT(is_equal(-dMin, dMin, 0, 2)); EXPECT(is_equal(dMin, 0, 0, 1)); EXPECT(is_equal(0, dMin, 0, 1)); EXPECT(is_equal(-dMin, 0, 0, 1)); EXPECT(is_equal(0, -dMin, 0, 1)); EXPECT(!is_equal(dMin, -dMin, 0, 1)); EXPECT(!is_equal(-dMin, dMin, 0, 1)); EXPECT(!is_equal(dMin, 0, 0, 0)); EXPECT(!is_equal(0, dMin, 0, 0)); EXPECT(!is_equal(-dMin, 0, 0, 0)); EXPECT(!is_equal(0, -dMin, 0, 0)); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { eckit::maths::FloatingPointExceptions::enable_floating_point_exceptions(); return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/types/test_fixedstring.cc0000664000175000017500000003150215253173651021212 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/types/FixedString.h" #include "eckit/testing/Test.h" #ifdef __clang__ #pragma clang diagnostic ignored "-Wself-assign-overloaded" #endif using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_constructor_zero") { // What happens if we create a length-zero FixedString? FixedString<0> fs; EXPECT(fs.length() == 0); EXPECT(fs.size() == 0); EXPECT(fs.asString() == ""); EXPECT(std::string(fs) == ""); FixedString<0> fs2(""); EXPECT(fs2.length() == 0); EXPECT(fs2.size() == 0); EXPECT(fs2.asString() == ""); EXPECT(std::string(fs2) == ""); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_constructor_default") { FixedString<8> fs; EXPECT(fs.length() == 0); EXPECT(fs.asString() == ""); EXPECT(std::string(fs) == ""); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_constructor_string") { // Construct with a string of the correct length std::string str1("12345678"); FixedString<8> fs1(str1); EXPECT(fs1.size() == 8); EXPECT(fs1.length() == 8); EXPECT(fs1.asString() == "12345678"); EXPECT(std::string(fs1) == "12345678"); // Construct with a string that is too short std::string str2("1234"); FixedString<8> fs2(str2); EXPECT(fs2.size() == 8); EXPECT(fs2.length() == 4); EXPECT(fs2.asString() == "1234"); EXPECT(std::string(fs2) == "1234"); // Construct with a string that is too long // n.b. use functor to make exception thrown in constructor palatable to EXPECT_ THR OW struct fs3_allocate { void operator()() { std::string str3("1234567890"); FixedString<8> fs3(str3); } }; EXPECT_THROWS_AS(fs3_allocate()(), AssertionFailed); // Construct with a zero length string std::string str4; FixedString<8> fs4(str4); EXPECT(fs4.size() == 8); EXPECT(fs4.length() == 0); EXPECT(fs4.asString() == ""); EXPECT(std::string(fs4) == ""); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_constructor_char") { // Construct with a string of the correct length FixedString<8> fs1("12345678"); EXPECT(fs1.size() == 8); EXPECT(fs1.length() == 8); EXPECT(fs1.asString() == "12345678"); EXPECT(std::string(fs1) == "12345678"); // Construct with a string that is too short FixedString<8> fs2("1234"); EXPECT(fs2.size() == 8); EXPECT(fs2.length() == 4); EXPECT(fs2.asString() == "1234"); EXPECT(std::string(fs2) == "1234"); // Construct with a string that is too long // n.b. use functor to make exception thrown in constructor palatable to EXPECT_ THR OW struct fs3_allocate { void operator()() { FixedString<8> fs3("1234567890"); } }; EXPECT_THROWS_AS(fs3_allocate()(), AssertionFailed); // Construct with a zero length string FixedString<8> fs4(""); EXPECT(fs4.size() == 8); EXPECT(fs4.length() == 0); EXPECT(fs4.asString() == ""); EXPECT(std::string(fs4) == ""); // Construct with a null pointer struct fs5_allocate { void operator()() { FixedString<8> fs5(nullptr); } }; EXPECT_THROWS_AS(fs5_allocate()(), AssertionFailed); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_constructor_fixedstring") { // Construct with a string of the correct length /// @note for all of these assignments, except for the <8> --> <8> the std::string constructor is used. It might /// be better for the long term if a template FixedString(const FixedString& ) /// constructor existed. std::string str1("12345678"); FixedString<8> fs1(str1); FixedString<8> fs1a(fs1); EXPECT(fs1a.size() == 8); EXPECT(fs1a.length() == 8); EXPECT(fs1a.asString() == "12345678"); EXPECT(std::string(fs1a) == "12345678"); // Construct with a string that is too short std::string str2("1234"); FixedString<4> fs2(str2); FixedString<8> fs2a(fs2); EXPECT(fs2a.size() == 8); EXPECT(fs2a.length() == 4); EXPECT(fs2a.asString() == "1234"); EXPECT(std::string(fs2a) == "1234"); // Construct with a string that is too long // n.b. use functor to make exception thrown in constructor palatable to EXPECT_ THR OW struct fs3_allocate { void operator()() { std::string str3("1234567890"); FixedString<10> fs3(str3); FixedString<8> fs3a(fs3); } }; EXPECT_THROWS_AS(fs3_allocate()(), AssertionFailed); // Construct with a zero length string std::string str4; FixedString<0> fs4(str4); FixedString<8> fs4a(fs4); EXPECT(fs4a.size() == 8); EXPECT(fs4a.length() == 0); EXPECT(fs4a.asString() == ""); EXPECT(std::string(fs4a) == ""); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_assignment_string") { // Construct with a string of the correct length FixedString<8> fs1 = std::string("12345678"); EXPECT(fs1.size() == 8); EXPECT(fs1.length() == 8); EXPECT(fs1.asString() == "12345678"); EXPECT(std::string(fs1) == "12345678"); // Construct with a string that is too short FixedString<8> fs2 = std::string("1234"); EXPECT(fs2.size() == 8); EXPECT(fs2.length() == 4); EXPECT(fs2.asString() == "1234"); EXPECT(std::string(fs2) == "1234"); // Construct with a string that is too long // n.b. use functor to make exception thrown in assignment palatable to EXPECT_ THR OW struct fs3_assign { void operator()() { FixedString<8> fs3 = std::string("1234567890"); (void)fs3; } }; EXPECT_THROWS_AS(fs3_assign()(), AssertionFailed); // Construct with a zero length string FixedString<8> fs4 = std::string(); EXPECT(fs4.size() == 8); EXPECT(fs4.length() == 0); EXPECT(fs4.asString() == ""); EXPECT(std::string(fs4) == ""); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_assignment_fixedstring") { // Construct with a string of the correct length /// @note for all of these assignments, except for the <8> --> <8> the std::string constructor is used. It might /// be better for the long term if a template FixedString(const FixedString& ) /// constructor existed. FixedString<8> fs1("12345678"); FixedString<8> fs1a = fs1; EXPECT(fs1a.size() == 8); EXPECT(fs1a.length() == 8); EXPECT(fs1a.asString() == "12345678"); EXPECT(std::string(fs1a) == "12345678"); /// Mismatched size assignments are (correctly) compile-time disallowed. /// // Construct with a string that is too short /// FixedString<4> fs2("1234"); /// FixedString<8> fs2a = fs2; /// EXPECT( fs2a.size() == 8 ); /// EXPECT( fs2a.length() == 4 ); /// EXPECT( fs2a.asString() == "1234" ); /// EXPECT( std::string(fs2a) == "1234" ); /// // Construct with a string that is too long /// // n.b. use functor to make exception thrown in constructor palatable to EXPECT_ THR OW /// struct fs3_allocate { /// void operator()() { /// FixedString<10> fs3("1234567890"); /// FixedString<8> fs3a = fs3; /// } /// }; /// EXPECT_THROWS_AS( fs3_allocate()(), AssertionFailed ); /// // Construct with a zero length string /// FixedString<0> fs4; /// FixedString<8> fs4a = fs4; /// EXPECT( fs4a.size() == 8 ); /// EXPECT( fs4a.length() == 0 ); /// EXPECT( fs4a.asString() == "" ); /// EXPECT( std::string(fs4a) == "" ); // self-assignment is ok fs1a = fs1a; EXPECT(fs1a.size() == 8); EXPECT(fs1a.length() == 8); EXPECT(fs1a.asString() == "12345678"); EXPECT(std::string(fs1a) == "12345678"); } //---------------------------------------------------------------------------------------------------------------------- // // Test comparison operators // //---------------------------------------------------------------------------------------------------------------------- /// @note Comparisons between different FixedString, FixedString not permitted at compile time. CASE("test_eckit_types_fixedstring_operators") { FixedString<8> fs1("12345678"); FixedString<8> fs2("12345678"); FixedString<8> fs3("abcdefgh"); // == EXPECT(fs1 == fs1); EXPECT(fs1 == fs2); EXPECT(!(fs1 == fs3)); // != EXPECT(!(fs1 != fs1)); EXPECT(!(fs1 != fs2)); EXPECT(fs1 != fs3); // < EXPECT(!(fs1 < fs1)); EXPECT(!(fs1 < fs2)); EXPECT(fs1 < fs3); EXPECT(!(fs3 < fs1)); // > EXPECT(!(fs1 > fs1)); EXPECT(!(fs1 > fs2)); EXPECT(!(fs1 > fs3)); EXPECT(fs3 > fs1); // <= EXPECT(fs1 <= fs1); EXPECT(fs1 <= fs2); EXPECT(fs1 <= fs3); EXPECT(!(fs3 <= fs1)); // >= EXPECT(fs1 >= fs1); EXPECT(fs1 >= fs2); EXPECT(!(fs1 >= fs3)); EXPECT(fs3 >= fs1); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_operators_zero") { FixedString<0> fs1; FixedString<0> fs2; // == EXPECT(fs1 == fs1); EXPECT(fs1 == fs2); // != EXPECT(!(fs1 != fs1)); EXPECT(!(fs1 != fs2)); // < EXPECT(!(fs1 < fs1)); EXPECT(!(fs1 < fs2)); // > EXPECT(!(fs1 > fs1)); EXPECT(!(fs1 > fs2)); // <= EXPECT(fs1 <= fs1); EXPECT(fs1 <= fs2); // >= EXPECT(fs1 >= fs1); EXPECT(fs1 >= fs2); } //---------------------------------------------------------------------------------------------------------------------- // // Test extraneous bits // //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_byte_size") { FixedString<0> fs0; FixedString<1> fs1; FixedString<4> fs4; FixedString<8> fs8; EXPECT(sizeof(fs0) == 0); EXPECT(sizeof(fs1) == 1); EXPECT(sizeof(fs4) == 4); EXPECT(sizeof(fs8) == 8); EXPECT(sizeof(FixedString<0>) == 0); EXPECT(sizeof(FixedString<1>) == 1); EXPECT(sizeof(FixedString<4>) == 4); EXPECT(sizeof(FixedString<8>) == 8); EXPECT(fs0.size() == 0); EXPECT(fs1.size() == 1); EXPECT(fs4.size() == 4); EXPECT(fs8.size() == 8); EXPECT(FixedString<0>::static_size() == 0); EXPECT(FixedString<1>::static_size() == 1); EXPECT(FixedString<4>::static_size() == 4); EXPECT(FixedString<8>::static_size() == 8); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_data_access") { FixedString<8> fs("12345678"); // Check that const and non-const pointers give access to same data... char* d = fs.data(); const char* cd = fs.data(); // EXPECT does a string comparison for cha r* EXPECT(d == cd); // Check that if we insert a \0 character appropriately, the accessible strings and lengths adjust correctly EXPECT(std::string(fs) == "12345678"); EXPECT(fs.length() == 8); d[4] = '\0'; EXPECT(std::string(fs) == "1234"); EXPECT(fs.length() == 4); d[6] = '\0'; d[4] = 'F'; EXPECT(std::string(fs) == "1234F6"); EXPECT(fs.length() == 6); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_fixedstring_reassign_shorter_string") { ///< @see ECKIT-182 FixedString<64> fs; fs = std::string("calvin"); EXPECT(fs == std::string("calvin")); /* this worked */ fs = std::string("calvin & hobbes"); EXPECT(fs == std::string("calvin & hobbes")); /* assinging longer string also worked */ fs = std::string("susie"); EXPECT(fs == std::string("susie")); /* but then assigning shorter did not, resulted "susien & hobbes" */ } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/test_uuid.cc0000664000175000017500000000354515253173651017640 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/exception/Exceptions.h" #include "eckit/types/UUID.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_uuid_default_constructor") { UUID uuid; EXPECT(uuid.size() == 16); EXPECT(uuid.isNil()); std::string res("00000000000000000000000000000000"); EXPECT(res == uuid.asString()); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_uuid_constructor_string") { std::string s("4b4053dc93e0b52a6f028cb36649d229"); UUID uuid(s); EXPECT(uuid.size() == 16); EXPECT(!uuid.isNil()); EXPECT(s == uuid.asString()); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_uuid_fromstring") { std::string s("4b4053dc93e0b52a6f028cb36649d229"); UUID uuid; EXPECT(uuid.isNil()); EXPECT_NO_THROW(uuid.fromString(s)); EXPECT(!uuid.isNil()); EXPECT(s == uuid.asString()); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_types_uuid_constructor_string_trow") { std::string s("4b405"); EXPECT_THROWS_AS(UUID uuid(s), eckit::AssertionFailed); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/test-double-compare-speed.cc0000664000175000017500000000244115253173651022576 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/log/BigNum.h" #include "eckit/log/Timer.h" #include "eckit/types/FloatCompare.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- void compare(size_t n) { for (size_t i = 0; i < n; ++i) { double x = (double)::rand() / (double)RAND_MAX; eckit::types::is_approximately_equal(x, double(0.5)); } } //---------------------------------------------------------------------------------------------------------------------- CASE("TestDoubleCompareSpeed") { const size_t n = 30000000; // with this data set, on a modern cpu we expect > 25E6 /s eckit::Timer t; compare(n); eckit::Log::info() << "Double compare speed: " << eckit::BigNum(n / t.elapsed()) << " /s" << std::endl; } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/test_cache.cc0000664000175000017500000001056015253173651017730 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/config/Resource.h" #include "eckit/config/ResourceMgr.h" #include "eckit/container/Cache.h" #include "eckit/log/Log.h" #include "eckit/types/Types.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { struct Obj { Obj() : s_(), d_(0) {} Obj(const string& s, const unsigned long long& d) : s_(s), d_(d) {} string s_; unsigned long long d_; friend std::ostream& operator<<(std::ostream& s, const Obj& x) { s << x.s_ << ":" << x.d_; return s; } }; //---------------------------------------------------------------------------------------------------------------------- CASE("test_contructor") { Cache cache; EXPECT(cache.insert("a", Obj("aaa", 11111))); EXPECT(cache.insert("b", Obj("bbb", 22222))); EXPECT(cache.insert("c", Obj("ccc", 33333))); // cache.print(std::cout); EXPECT(cache.valid("a")); EXPECT(cache.valid("b")); EXPECT(cache.valid("c")); cache.clear(); EXPECT(!cache.valid("a")); EXPECT(!cache.valid("b")); EXPECT(!cache.valid("c")); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_expire") { Cache cache; EXPECT(cache.insert("a", Obj("aaa", 11111))); EXPECT(cache.insert("b", Obj("bbb", 22222))); EXPECT(cache.insert("c", Obj("ccc", 33333))); cache.expire("b"); // cache.print(std::cout); EXPECT(cache.size() == 3); EXPECT(cache.valid("a")); EXPECT(!cache.valid("b")); EXPECT(cache.valid("c")); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_purge") { Cache cache; EXPECT(cache.insert("a", Obj("aaa", 11111))); EXPECT(cache.insert("b", Obj("bbb", 22222))); EXPECT(cache.insert("c", Obj("ccc", 33333))); EXPECT(cache.insert("d", Obj("ddd", 44444))); EXPECT(cache.size() == 4); EXPECT(cache.expire("b")); EXPECT(cache.expire("d")); cache.purge(); cache.print(std::cout); EXPECT(cache.size() == 2); EXPECT(cache.valid("a")); EXPECT(!cache.valid("b")); EXPECT(cache.valid("c")); EXPECT(!cache.valid("d")); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_update") { Cache cache; EXPECT(cache.insert("a", Obj("aaa", 11111))); EXPECT(cache.insert("b", Obj("bbb", 22222))); EXPECT(cache.insert("c", Obj("ccc", 33333))); Obj o1("ddd", 44444); EXPECT(!cache.update("d", o1)); Obj o2("BBB", 2); EXPECT(cache.update("b", o2)); Obj o3; EXPECT(cache.fetch("d", o3)); EXPECT(o3.s_ == "ddd"); EXPECT(o3.d_ == 44444); EXPECT(cache.fetch("b", o3)); EXPECT(o3.s_ == "BBB"); EXPECT(o3.d_ == 2); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_fetch") { Cache cache; EXPECT(cache.insert("a", Obj("aaa", 11111))); EXPECT(cache.insert("b", Obj("bbb", 22222))); EXPECT(cache.insert("c", Obj("ccc", 33333))); EXPECT(cache.insert("d", Obj("ddd", 44444))); Obj o; EXPECT(!cache.fetch("f", o)); // no obj f exists EXPECT(cache.fetch("b", o)); // obj b exists EXPECT(o.s_ == "bbb"); EXPECT(o.d_ == 22222); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_insert") { Cache cache; EXPECT(cache.insert("a", Obj("aaa", 11111))); EXPECT(cache.insert("b", Obj("bbb", 22222))); EXPECT(cache.insert("c", Obj("ccc", 33333))); // double insert fails EXPECT(!cache.insert("a", Obj("AAA", 1))); Obj o; EXPECT(cache.fetch("a", o)); EXPECT(o.s_ != "AAA"); EXPECT(o.d_ != 1); EXPECT(o.s_ == "aaa"); EXPECT(o.d_ == 11111); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/types/test_print_vector.cc0000664000175000017500000000447415253173651021412 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/log/Log.h" #include "eckit/types/Types.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_print_vector_string") { vector vstr; vstr.push_back("Hello"); vstr.push_back("World"); vstr.push_back("test"); vstr.push_back("case"); stringstream s; s << vstr; EXPECT("[Hello,World,test,case]" == s.str()); } CASE("test_eckit_print_vector_pair") { vector > vpair; vpair.push_back(make_pair("k1", 123)); vpair.push_back(make_pair("k1", 124)); vpair.push_back(make_pair("k1", 125)); vpair.push_back(make_pair("k2", 125)); stringstream s; s << vpair; EXPECT("[,,,]" == s.str()); } CASE("test_eckit_print_vector_ints") { vector vint; vint.push_back(123); vint.push_back(124); vint.push_back(125); vint.push_back(126); vint.push_back(127); vint.push_back(129); vint.push_back(131); vint.push_back(133); vint.push_back(135); vint.push_back(135); vint.push_back(135); vint.push_back(135); vint.push_back(1); stringstream s; s << vint; EXPECT("[123-127,129-135-2,3*135,1]" == s.str()); } CASE("test_eckit_print_vector_doubs") { // These should not contract into ranges. vector vdoub; vdoub.push_back(123.0); vdoub.push_back(124.0); vdoub.push_back(125.0); vdoub.push_back(126.0); vdoub.push_back(127.0); vdoub.push_back(129.0); vdoub.push_back(131.0); vdoub.push_back(133.0); vdoub.push_back(135.0); vdoub.push_back(135.0); vdoub.push_back(135.0); vdoub.push_back(135.0); vdoub.push_back(1.0); stringstream s; s << vdoub; EXPECT("[123,124,125,126,127,129,131,133,135,135,135,135,1]" == s.str()); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/CMakeLists.txt0000664000175000017500000000206115253173651016710 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 add_subdirectory( config ) add_subdirectory( container ) add_subdirectory( exception ) add_subdirectory( filesystem ) add_subdirectory( geometry ) add_subdirectory( io ) add_subdirectory( large_file ) add_subdirectory( linalg ) add_subdirectory( log ) add_subdirectory( maths ) add_subdirectory( memory ) add_subdirectory( mpi ) add_subdirectory( option ) add_subdirectory( parser ) add_subdirectory( runtime ) add_subdirectory( serialisation ) add_subdirectory( system ) add_subdirectory( testing ) add_subdirectory( thread ) add_subdirectory( types ) add_subdirectory( utils ) add_subdirectory( value ) if( eckit_HAVE_ECKIT_SQL ) add_subdirectory( sql ) endif() if( eckit_HAVE_ECKIT_CODEC ) add_subdirectory( codec ) endif() if( eckit_HAVE_ECKIT_SPEC ) add_subdirectory( spec ) endif() if( eckit_HAVE_ECKIT_GEO ) add_subdirectory( geo ) endif() if( eckit_HAVE_EXPERIMENTAL ) add_subdirectory( experimental ) endif() eckit-2.3.0/tests/experimental/0000775000175000017500000000000015253173651016646 5ustar alastairalastaireckit-2.3.0/tests/experimental/singleton/0000775000175000017500000000000015253173651020650 5ustar alastairalastaireckit-2.3.0/tests/experimental/singleton/TestFactory.cc0000664000175000017500000000323615253173651023432 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "TestFactory.h" struct TestFactory::PImpl { using Store = std::map; Store register_; ~PImpl() { for (Store::iterator it = register_.begin(); it != register_.end(); ++it) { delete it->second; } } void regist(const std::string& name, TestBuilder* builder) { std::cout << "registering builder [" << name << "]" << std::endl; Store::iterator itr = register_.find(name); if (itr != register_.end()) { throw std::bad_alloc(); } register_[name] = builder; } TestBuilder& get(const std::string& name) { std::cout << "get builder [" << name << "]" << std::endl; Store::iterator itr = register_.find(name); if (itr == register_.end()) { throw std::bad_alloc(); } ASSERT(itr->second); return *(itr->second); } }; //---------------------------------------------------------------------------------------------------------------------- TestFactory::TestFactory() : pimpl_(new TestFactory::PImpl()) {} TestFactory& TestFactory::instance() { // autolock mutex static TestFactory factory; return factory; } void TestFactory::regist(const std::string& name, TestBuilder* builder) { pimpl_->regist(name, builder); } TestBuilder& TestFactory::get(const std::string& name) { return pimpl_->get(name); } eckit-2.3.0/tests/experimental/singleton/TestBuilder2.cc0000664000175000017500000000072415253173651023472 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "TestFactory.h" class TestBuilder2 : public TestBuilder { public: void build() { std::cout << "building a b2" << std::endl; } }; //---------------------------------------------------------------------------------------------------------------------- AutoRegistBuilder builder2("b2"); eckit-2.3.0/tests/experimental/singleton/TestBuilder1.cc0000664000175000017500000000072415253173651023471 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "TestFactory.h" class TestBuilder1 : public TestBuilder { public: void build() { std::cout << "building a b1" << std::endl; } }; //---------------------------------------------------------------------------------------------------------------------- AutoRegistBuilder builder1("b1"); eckit-2.3.0/tests/experimental/singleton/CMakeLists.txt0000664000175000017500000000214015253173651023405 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_append_to_rpath( "./" ) ecbuild_add_library( TARGET eckit_test_experimental_singleton_factory TYPE SHARED NOINSTALL SOURCES TestFactory.cc TestFactory.h PUBLIC_LIBS eckit ) ecbuild_add_library( TARGET eckit_test_experimental_singleton_builder1 TYPE SHARED NOINSTALL SOURCES TestBuilder1.cc PUBLIC_LIBS eckit_test_experimental_singleton_factory ) ecbuild_add_library( TARGET eckit_test_experimental_singleton_builder2 TYPE SHARED NOINSTALL SOURCES TestBuilder2.cc PUBLIC_LIBS eckit_test_experimental_singleton_factory ) ecbuild_add_test( TARGET eckit_test_experimental_singleton_singleton SOURCES test_singleton.cc LIBS eckit_test_experimental_singleton_builder1 eckit_test_experimental_singleton_builder2 ) eckit-2.3.0/tests/experimental/singleton/TestFactory.h0000664000175000017500000000152415253173651023272 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #ifndef TestFactory_h #define TestFactory_h #include #include #include class TestBuilder { public: virtual ~TestBuilder() {} virtual void build() = 0; }; class TestFactory { public: // methods TestFactory(); static TestFactory& instance(); void regist(const std::string&, TestBuilder*); TestBuilder& get(const std::string&); private: struct PImpl; std::unique_ptr pimpl_; }; template class AutoRegistBuilder { public: AutoRegistBuilder(const std::string& name) { std::cout << "auto register [" << name << "]" << std::endl; TestFactory::instance().regist(name, new T()); } }; #endif /* TestFactory_h */ eckit-2.3.0/tests/experimental/singleton/test_singleton.cc0000664000175000017500000000057115253173651024223 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "TestFactory.h" int main(int argc, char** argv) { TestBuilder& b1 = TestFactory::instance().get("b1"); TestBuilder& b2 = TestFactory::instance().get("b2"); b1.build(); b2.build(); return 0; } eckit-2.3.0/tests/experimental/CMakeLists.txt0000664000175000017500000000024015253173651021402 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 add_subdirectory( singleton ) eckit-2.3.0/tests/system/0000775000175000017500000000000015253173651015475 5ustar alastairalastaireckit-2.3.0/tests/system/CMakeLists.txt0000664000175000017500000000057115253173651020240 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # file accessed in the test file( WRITE "${CMAKE_BINARY_DIR}/etc/eckit/test/test.cfg" "Just for testing" ) ecbuild_add_test( TARGET eckit_test_system_library SOURCES test_system_library.cc LIBS eckit ) eckit-2.3.0/tests/system/test_system_library.cc0000664000175000017500000001025015253173651022111 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include #include #include "eckit/config/LibEcKit.h" #include "eckit/eckit_config.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/LocalPathName.h" #include "eckit/system/Library.h" #include "eckit/system/LibraryManager.h" #include "eckit/system/Plugin.h" #include "eckit/system/ResourceUsage.h" #include "eckit/system/SystemInfo.h" #include "eckit/testing/Test.h" using namespace std; using namespace eckit; using namespace eckit::testing; using eckit::system::Library; using eckit::system::LibraryManager; using eckit::system::Plugin; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- // Issue ECKIT-520 // Just adding this plugin and registering it should trigger the registration and deregistration. // before ISSUE-520 is fixed, the deregistration may cause a segfault as the debug log channel is // destroyed before the Plugin. class TestPlugin : Plugin { public: TestPlugin() : Plugin("test-plugin") {} ~TestPlugin() { std::cout << "~TestPlugin()" << std::endl; } static const TestPlugin& instance() { static TestPlugin instance; return instance; } std::string version() const override { return "0.0.0"; } std::string gitsha1(unsigned int count) const override { return "undefined"; } }; REGISTER_LIBRARY(TestPlugin); //---------------------------------------------------------------------------------------------------------------------- CASE("test_eckit_resource_usage_0") { size_t chunk = 20 * 1024 * 1024; for (size_t i = 1; i < 5; ++i) { size_t before = system::ResourceUsage().maxResidentSetSize(); void* m = ::malloc(chunk); ::memset(m, 0, chunk); size_t after = system::ResourceUsage().maxResidentSetSize(); ::free(m); Log::info() << "Memory usage " << after << std::endl; EXPECT(before <= after); } } CASE("test_eckit_system_info") { eckit::LocalPathName execPath; EXPECT_NO_THROW(execPath = eckit::system::SystemInfo::instance().executablePath()); EXPECT(std::string(execPath).size()); Log::info() << "execPath is " << execPath << std::endl; } CASE("test_eckit_system_library") { std::vector libs = LibraryManager::list(); for (std::vector::const_iterator libname = libs.begin(); libname != libs.end(); ++libname) { EXPECT_NO_THROW(LibraryManager::lookup(*libname)); const Library& lib = LibraryManager::lookup(*libname); EXPECT_NO_THROW(lib.prefixDirectory()); Log::info() << "Library " << lib.name() << " @ " << lib.prefixDirectory() << std::endl; Log::info() << lib << std::endl; } // this exercises the tilde expansion EXPECT_NO_THROW(LocalPathName("~eckit/etc").exists()); EXPECT_NO_THROW(LocalPathName("~eckit/etc/eckit/test/test.cfg").exists()); } CASE("test_libeckit") { EXPECT_NO_THROW(LibEcKit::instance().configuration()); // tests an empty configuration } #if eckit_HAVE_ECKIT_CMD CASE("test dynamic load") { EXPECT(!LibraryManager::exists("eckit_cmd")); EXPECT(LibraryManager::loadLibrary("eckit_cmd") != nullptr); EXPECT(LibraryManager::exists("eckit_cmd")); } #endif CASE("test fail dynamic load") { EXPECT(!LibraryManager::exists("fake-library")); EXPECT(LibraryManager::loadLibrary("fake-library") == nullptr); } #if eckit_HAVE_ECKIT_SQL CASE("Can load library without a eckit::Library object") { EXPECT(!LibraryManager::exists("eckit_sql")); EXPECT(LibraryManager::loadLibrary("eckit_sql") != nullptr); } #endif #if eckit_HAVE_ECKIT_MPI CASE("Fails to load a plugin without a eckit::Plugin object") { EXPECT(!LibraryManager::exists("eckit_mpi")); EXPECT_THROW_AS(LibraryManager::loadPlugin("eckit_mpi"), UnexpectedState); } #endif //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/config/0000775000175000017500000000000015253173651015416 5ustar alastairalastaireckit-2.3.0/tests/config/CMakeLists.txt0000664000175000017500000000062015253173651020154 0ustar alastairalastair# SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 ecbuild_add_test( TARGET eckit_test_config_resource SOURCES test_resource.cc ARGS -integer 100 -listlong 88,99,11,22 LIBS eckit ) ecbuild_add_test( TARGET eckit_test_config_configuration SOURCES test_configuration.cc LIBS eckit ) eckit-2.3.0/tests/config/test_resource.cc0000664000175000017500000000545115253173651020620 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/config/LibEcKit.h" #include "eckit/config/Resource.h" #include "eckit/config/ResourceMgr.h" #include "eckit/log/Bytes.h" #include "eckit/log/Log.h" #include "eckit/runtime/Tool.h" #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h" #include "eckit/types/Types.h" using namespace std; using namespace eckit; using namespace eckit::testing; using namespace eckit::types; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_default") { std::string s = Resource("s", "some"); EXPECT(s == "some"); double d = Resource("d", 777.7); EXPECT(is_approximately_equal(d, 777.7, 0.0001)); // accept 0.0001% tolerance } //---------------------------------------------------------------------------------------------------------------------- CASE("test_vector_long") { std::vector def(3, 77); std::vector v = Resource >("listlong;-listlong", def); EXPECT(v[0] == 88); EXPECT(v[1] == 99); EXPECT(v[2] == 11); EXPECT(v[3] == 22); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_command_line") { int myint = Resource("integer;-integer", 0); EXPECT(myint == 100); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_environment_var") { char v[] = "TEST_ENV_INT=333"; putenv(v); int intenv = Resource("intEnv;$TEST_ENV_INT", 777); EXPECT(intenv == 333); char foo[] = "FOO=1Mb"; putenv(foo); long l1 = Resource("$FOO", 0); EXPECT(l1 == 1024 * 1024); long l2 = Resource("$FOO;-foo", 0); EXPECT(l2 == 1024 * 1024); long l3 = Resource("-foo;$FOO", 0); EXPECT(l3 == 1024 * 1024); long l4 = Resource("$FOO;foo;-foo", 0); EXPECT(l4 == 1024 * 1024); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_libresource") { long i = LibResource("foo-bar;$FOOBAR", 1024 * 1024); EXPECT(i == 1024 * 1024); char v[] = "TEST_FOOBAZ=fooBAZ"; putenv(v); std::string foobaz = LibResource("foo-baz;$TEST_FOOBAZ", "foobazzzzzzz"); EXPECT(foobaz == "fooBAZ"); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return run_tests(argc, argv); } eckit-2.3.0/tests/config/test_configuration.cc0000664000175000017500000004252715253173651021645 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include #include "eckit/config/LocalConfiguration.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/filesystem/PathName.h" #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h" #include "eckit/utils/Hash.h" namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("test_configuration_interface") { bool value_bool = true; int value_int = 1; long value_long = 2; long long value_long_long = 2; size_t value_size_t = 3; float value_float = 1.234567; double value_double = 1.2345678912345789123456789; std::string value_string = "string"; std::vector value_arr_bool = {true, false, true}; std::vector value_arr_int = {1, 2, 3}; std::vector value_arr_long = {4, 5}; std::vector value_arr_long_long = {4, 5}; std::vector value_arr_size_t = {6, 7}; std::vector value_arr_float = {1.234567, 2.345678}; std::vector value_arr_double = {1.234567, 2.345678}; std::vector value_arr_string = {"hello", "world"}; std::int32_t value_int32 = value_int; std::int64_t value_int64 = value_long_long; std::vector value_arr_int32 = {4, 5}; std::vector value_arr_int64 = {4, 5}; bool result_bool = false; int result_int = 0; long result_long = 0; long long result_long_long = 0; size_t result_size_t = 0; float result_float = 0; double result_double = 0; std::string result_string; std::vector result_arr_bool; std::vector result_arr_int; std::vector result_arr_long; std::vector result_arr_long_long; std::vector result_arr_size_t; std::vector result_arr_float; std::vector result_arr_double; std::vector result_arr_string; std::int32_t result_int32 = 0; std::int64_t result_int64 = 0; std::vector result_arr_int32; std::vector result_arr_int64; LocalConfiguration local; { local.set("bool", value_bool); local.set("int", value_int); local.set("long", value_long); local.set("long long", value_long_long); local.set("size_t", value_size_t); local.set("float", value_float); local.set("double", value_double); local.set("string", value_string); local.set("arr_bool", value_arr_bool); local.set("arr_int", value_arr_int); local.set("arr_long", value_arr_long); local.set("arr_long_long", value_arr_long_long); local.set("arr_size_t", value_arr_size_t); local.set("arr_float", value_arr_float); local.set("arr_double", value_arr_double); local.set("arr_string", value_arr_string); local.set("int32", value_int32); local.set("int64", value_int64); local.set("arr_int32", value_arr_int32); local.set("arr_int64", value_arr_int64); } const Configuration& conf = local; EXPECT(!conf.get("missing", result_bool)); EXPECT(!conf.get("missing", result_int)); EXPECT(!conf.get("missing", result_long)); EXPECT(!conf.get("missing", result_long_long)); EXPECT(!conf.get("missing", result_size_t)); EXPECT(!conf.get("missing", result_float)); EXPECT(!conf.get("missing", result_double)); EXPECT(!conf.get("missing", result_string)); EXPECT(!conf.get("missing", result_arr_bool)); EXPECT(!conf.get("missing", result_arr_int)); EXPECT(!conf.get("missing", result_arr_long)); EXPECT(!conf.get("missing", result_arr_long_long)); EXPECT(!conf.get("missing", result_arr_size_t)); EXPECT(!conf.get("missing", result_arr_float)); EXPECT(!conf.get("missing", result_arr_double)); EXPECT(!conf.get("missing", result_arr_string)); EXPECT(!conf.get("missing", result_int32)); EXPECT(!conf.get("missing", result_int64)); EXPECT(!conf.get("missing", result_arr_int32)); EXPECT(!conf.get("missing", result_arr_int64)); EXPECT(conf.get("bool", result_bool)); EXPECT(conf.get("int", result_int)); EXPECT(conf.get("long", result_long)); EXPECT(conf.get("long long", result_long_long)); EXPECT(conf.get("size_t", result_size_t)); EXPECT(conf.get("float", result_float)); EXPECT(conf.get("double", result_double)); EXPECT(conf.get("string", result_string)); EXPECT(conf.get("arr_bool", result_arr_bool)); EXPECT(conf.get("arr_int", result_arr_int)); EXPECT(conf.get("arr_long", result_arr_long)); EXPECT(conf.get("arr_long_long", result_arr_long_long)); EXPECT(conf.get("arr_size_t", result_arr_size_t)); EXPECT(conf.get("arr_float", result_arr_float)); EXPECT(conf.get("arr_double", result_arr_double)); EXPECT(conf.get("arr_string", result_arr_string)); EXPECT(conf.get("int32", result_int32)); EXPECT(conf.get("int64", result_int64)); EXPECT(conf.get("arr_int32", result_arr_int32)); EXPECT(conf.get("arr_int64", result_arr_int64)); EXPECT(result_bool == value_bool); EXPECT(result_int == value_int); EXPECT(result_long == value_long); EXPECT(result_long_long == value_long_long); EXPECT(result_size_t == value_size_t); EXPECT(result_float == value_float); EXPECT(result_double == value_double); EXPECT(result_string == value_string); EXPECT(result_arr_bool == value_arr_bool); EXPECT(result_arr_int == value_arr_int); EXPECT(result_arr_long_long == value_arr_long_long); EXPECT(result_arr_size_t == value_arr_size_t); EXPECT(result_arr_float == value_arr_float); EXPECT(result_arr_double == value_arr_double); EXPECT(result_arr_string == value_arr_string); EXPECT(result_arr_int64 == value_arr_int64); EXPECT(conf.getBool("bool") == value_bool); EXPECT(conf.getInt("int") == value_int); EXPECT(conf.getLong("long") == value_long); EXPECT(conf.getLong("long long") == value_long_long); EXPECT(conf.getUnsigned("size_t") == value_size_t); EXPECT(conf.getFloat("float") == value_float); EXPECT(conf.getDouble("double") == value_double); EXPECT(conf.getString("string") == value_string); EXPECT(conf.getBoolVector("arr_bool") == value_arr_bool); EXPECT(conf.isBooleanList("arr_bool")); EXPECT(!conf.isBooleanList("arr_int")); EXPECT(!conf.isBooleanList("arr_string")); EXPECT(conf.isConvertible>("arr_bool")); EXPECT(!conf.isConvertible>("arr_int")); EXPECT(!conf.isConvertible>("arr_string")); EXPECT(conf.getIntVector("arr_int") == value_arr_int); EXPECT(conf.getLongVector("arr_long") == value_arr_long); EXPECT(conf.getUnsignedVector("arr_size_t") == value_arr_size_t); EXPECT(conf.getFloatVector("arr_float") == value_arr_float); EXPECT(conf.getDoubleVector("arr_double") == value_arr_double); EXPECT(conf.getStringVector("arr_string") == value_arr_string); EXPECT(conf.getInt32("int32") == value_int32); EXPECT(conf.getInt64("int64") == value_int64); EXPECT(conf.getInt32Vector("arr_int32") == value_arr_int32); EXPECT(conf.getInt64Vector("arr_int64") == value_arr_int64); // Get throwing EXPECT_THROWS_AS(conf.getBool("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getInt("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getLong("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getUnsigned("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getFloat("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getDouble("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getString("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getBoolVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getIntVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getLongVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getUnsignedVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getFloatVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getDoubleVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getStringVector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getInt32("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getInt64("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getInt32Vector("missing"), eckit::Exception); EXPECT_THROWS_AS(conf.getInt64Vector("missing"), eckit::Exception); // Get with default values instead of throwing EXPECT(conf.getBool("missing", value_bool) == value_bool); EXPECT(conf.getInt("missing", value_int) == value_int); EXPECT(conf.getLong("missing", value_long) == value_long); EXPECT(conf.getUnsigned("missing", value_size_t) == value_size_t); EXPECT(conf.getFloat("missing", value_float) == value_float); EXPECT(conf.getDouble("missing", value_double) == value_double); EXPECT(conf.getString("missing", value_string) == value_string); EXPECT(conf.getBoolVector("missing", value_arr_bool) == value_arr_bool); EXPECT(conf.getIntVector("missing", value_arr_int) == value_arr_int); EXPECT(conf.getLongVector("missing", value_arr_long) == value_arr_long); EXPECT(conf.getUnsignedVector("missing", value_arr_size_t) == value_arr_size_t); EXPECT(conf.getFloatVector("missing", value_arr_float) == value_arr_float); EXPECT(conf.getDoubleVector("missing", value_arr_double) == value_arr_double); EXPECT(conf.getStringVector("missing", value_arr_string) == value_arr_string); EXPECT(conf.getInt32("missing", value_int32) == value_int32); EXPECT(conf.getInt64("missing", value_int64) == value_int64); EXPECT(conf.getInt32Vector("missing", value_arr_int32) == value_arr_int32); EXPECT(conf.getInt64Vector("missing", value_arr_int64) == value_arr_int64); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_yaml_empty") { PathName yamlpath = "test_yaml_empty.yaml"; std::string yamlstr( "office:\n" " manager :\n"); { std::ofstream yamlfile(yamlpath.localPath()); yamlfile << yamlstr; } LocalConfiguration emptyconf; // LocalConfiguration is empty EXPECT(emptyconf.empty()); YAMLConfiguration conf(yamlpath); LocalConfiguration office(conf, "office"); // LocalConfiguration has content EXPECT(!office.empty()); // Not sure this is correct yaml but since the parser accepts it we need to handle it LocalConfiguration nilconf(office, "manager"); // LocalConfiguration is nil EXPECT(nilconf.empty()); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_json_configuration") { PathName jsonpath = "test_json_configuration.json"; std::string jsonstr( "\n" "{" "\n" " \"manager\" : { \"name\" : \"Sidonia\" , \"office\" : 1 }," "\n" " \"staff\" : [" "\n" " { \"name\" : \"Suske\" , \"office\" : 2 }," "\n" " { \"name\" : \"Wiske\" , \"office\" : 3 }" "\n" " ]" "\n" "}" "\n"); { std::ofstream jsonfile(jsonpath.localPath()); jsonfile << jsonstr; } YAMLConfiguration conf_from_str(jsonstr); YAMLConfiguration conf(jsonpath); LocalConfiguration manager; std::vector staff; EXPECT(conf.get("manager", manager)); EXPECT(conf.get("staff", staff)); std::string name; int office; EXPECT(manager.get("name", name)); EXPECT(manager.get("office", office)); EXPECT(name == std::string("Sidonia")); EXPECT(office == 1); EXPECT(staff[0].get("name", name)); EXPECT(staff[0].get("office", office)); EXPECT(name == std::string("Suske")); EXPECT(office == 2); EXPECT(staff[1].get("name", name)); EXPECT(staff[1].get("office", office)); EXPECT(name == std::string("Wiske")); EXPECT(office == 3); } //---------------------------------------------------------------------------------------------------------------------- CASE("YAML configuration converts numbers to strings or numbers") { const char* text = R"YAML( --- base: one : "1" two : "2" three : 3 four : 4 neg7 : "-7" )YAML"; std::string cfgtxt(text); YAMLConfiguration conf(cfgtxt); LocalConfiguration local; EXPECT_NO_THROW(conf.get("base", local)); std::cerr << Colour::green << local << Colour::reset << std::endl; EXPECT_EQUAL(local.getString("one"), "1"); EXPECT_EQUAL(local.getString("two"), "2"); EXPECT_EQUAL(local.getString("three"), "3"); EXPECT_EQUAL(local.getString("four"), "4"); EXPECT_EQUAL(local.getInt("one"), 1); EXPECT_EQUAL(local.getLong("two"), 2); EXPECT_EQUAL(local.getInt32("three"), 3); EXPECT_EQUAL(local.getInt64("four"), 4); EXPECT_EQUAL(local.getString("neg7"), "-7"); EXPECT_EQUAL(local.getInt("neg7"), -7); } //---------------------------------------------------------------------------------------------------------------------- CASE("YAML configuration with null value") { const char* text = R"YAML( --- base: nothing : null )YAML"; std::string cfgtxt(text); YAMLConfiguration conf(cfgtxt); LocalConfiguration local; EXPECT_NO_THROW(conf.get("base", local)); std::cerr << Colour::green << local << Colour::reset << std::endl; EXPECT(local.isNull("nothing")); } //---------------------------------------------------------------------------------------------------------------------- CASE("test_local_configuration") { LocalConfiguration local; { LocalConfiguration manager; manager.set("name", "Sidonia"); manager.set("office", 1); manager.set("height", 1.78); manager.set("free", false); std::vector staff(2); staff[0].set("name", "Suske"); staff[1].set("name", "Wiske"); staff[0].set("office", 2); staff[1].set("office", 3); local.set("manager", manager); local.set("staff", staff); local.set("books.count", 10); } const Configuration& conf = local; LocalConfiguration manager; std::vector staff; EXPECT(conf.get("manager", manager)); EXPECT(conf.isSubConfigurationList("staff")); EXPECT(conf.isConvertible("staff", staff)); EXPECT(conf.get("staff", staff)); std::string name; int office; EXPECT(manager.get("name", name)); EXPECT(manager.get("office", office)); EXPECT(name == std::string("Sidonia")); EXPECT(office == 1); EXPECT(staff[0].get("name", name)); EXPECT(staff[0].get("office", office)); EXPECT(name == std::string("Suske")); EXPECT(office == 2); EXPECT(staff[1].get("name", name)); EXPECT(staff[1].get("office", office)); EXPECT(name == std::string("Wiske")); EXPECT(office == 3); int books_count; EXPECT(conf.has("books")); EXPECT(conf.get("books.count", books_count)); EXPECT(books_count == 10); LocalConfiguration books; EXPECT(conf.isSubConfiguration("books")); conf.get("books", books); EXPECT(books.getInt("count") == 10); EXPECT(conf.isConvertible("books")); EXPECT(conf.isList("staff")); EXPECT(conf.isIntegral("manager.office")); EXPECT(!conf.isFloatingPoint("manager.office")); EXPECT(conf.isConvertible("manager.office")); EXPECT(conf.isConvertible("manager.office")); EXPECT(conf.isConvertible("manager.office")); EXPECT(conf.isConvertible("manager.office")); EXPECT(conf.isConvertible("manager.office")); EXPECT(conf.isConvertible("manager.office")); EXPECT(!conf.isConvertible("manager.office")); EXPECT(!conf.isConvertible("manager.office")); EXPECT(!conf.isConvertible("manager.office")); EXPECT(conf.isConvertible("manager.height")); EXPECT(conf.isConvertible("manager.height")); EXPECT(!conf.isConvertible("manager.height")); EXPECT(!conf.isConvertible("manager.height")); EXPECT(!conf.isConvertible("manager.height")); EXPECT(!conf.isConvertible("manager.height")); double manager_height; EXPECT(conf.get("manager.height", manager_height)); EXPECT(manager_height == 1.78); local.set("a", "a"); const eckit::Parametrisation& p = conf; EXPECT(!p.has("a.b")); } CASE("Hash a configuration") { std::unique_ptr h(eckit::HashFactory::instance().build("MD5")); LocalConfiguration cfg; cfg.set("name", "Sidonia"); cfg.set("office", 1); cfg.hash(*h); // std::cout << "MD5 " << h->digest() << std::endl; EXPECT(h->digest() == "9f060b35735e98b0fdc0bf4c2d6d6d8d"); } //---------------------------------------------------------------------------------------------------------------------- } // namespace eckit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } eckit-2.3.0/tests/value/0000775000175000017500000000000015253173651015265 5ustar alastairalastaireckit-2.3.0/tests/value/AnyKeyParams.h0000664000175000017500000000212415253173651020001 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 /// @file AnyKeyParams.h /// @author Florian Rathgeber /// @date March 2015 #ifndef eckit_test_AnyKeyParams_H #define eckit_test_AnyKeyParams_H #include "eckit/serialisation/Stream.h" #include "eckit/value/Params.h" //----------------------------------------------------------------------------- namespace eckit_test { //----------------------------------------------------------------------------- struct AnyKeyParams { AnyKeyParams(const std::string& payload); AnyKeyParams(eckit::Stream& s); static const char* className() { return "AnyKeyParams"; } private: friend void encode(const AnyKeyParams&, eckit::Stream&); std::string payload_; }; eckit::Params::value_t getValue(const AnyKeyParams&, const eckit::Params::key_t&); void print(const AnyKeyParams&, std::ostream&); void encode(const AnyKeyParams& p, eckit::Stream& s); //----------------------------------------------------------------------------- } // namespace eckit_test #endif eckit-2.3.0/tests/value/test_value_nil.cc0000664000175000017500000002476115253173651020623 0ustar alastairalastair// SPDX-FileCopyrightText: 1996- European Centre for Medium-Range Weather Forecasts (ECMWF) // SPDX-License-Identifier: Apache-2.0 #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h" #include "eckit/value/Value.h" #include "test_value_helper.h" using namespace std; using namespace eckit; using namespace eckit::testing; using namespace eckit::test::value_helper; using eckit::types::is_approximately_equal; namespace eckit::test { //---------------------------------------------------------------------------------------------------------------------- CASE("Nil cast correctly in/out of Value") { Value val_nil; // // Access and conversion of nil // EXPECT(val_nil.typeName() == "Nil"); EXPECT(val_nil.isNil()); // Valid conversions // Invalid conversions EXPECT_THROWS_AS(val_nil.as(), BadConversion); EXPECT_THROWS_AS(val_nil.as(), BadConversion); EXPECT_THROWS_AS(val_nil.as(), BadConversion); EXPECT_THROWS_AS(val_nil.as