diff --git a/.github/workflows/testmatrix.yml b/.github/workflows/testmatrix.yml index df4569e32a..1225f71ca1 100644 --- a/.github/workflows/testmatrix.yml +++ b/.github/workflows/testmatrix.yml @@ -6,6 +6,7 @@ on: - 'docs/**' - readme.* - README.* + - 'CHANGES.txt' - '*.md' - '*.svg' - '*.png' @@ -17,6 +18,7 @@ on: - 'docs/**' - readme.* - README.* + - 'CHANGES.txt' - '*.md' - '*.svg' - '*.png' @@ -34,8 +36,8 @@ jobs: build: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - os: [ubuntu-22.04] + python-version: ["3.11", "3.12", "3.13", "3.14"] + os: [ubuntu-24.04] # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix @@ -43,10 +45,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -82,7 +84,9 @@ jobs: - name: Tests # Execute C++ and Python tests run: | - BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data ./build/tests/bag_tests + export BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data + export BAG_HOME=${{github.workspace}}/configdata + ./build/tests/bag_tests source python-venv/bin/activate - BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data python -m pytest --cov=bagPy --cov-branch --cov-report=xml --junitxml=python-test-results.xml python/test_*.py + python -m pytest --cov=bagPy --cov-branch --cov-report=xml --junitxml=python-test-results.xml python/test_*.py deactivate diff --git a/.github/workflows/testreporting.yml b/.github/workflows/testreporting.yml index 66178af483..0b87407c8f 100644 --- a/.github/workflows/testreporting.yml +++ b/.github/workflows/testreporting.yml @@ -6,6 +6,7 @@ on: - 'docs/**' - readme.* - README.* + - 'CHANGES.txt' - '*.md' - '*.svg' - .github/workflows/testmatrix.yml @@ -16,6 +17,7 @@ on: - 'docs/**' - readme.* - README.* + - 'CHANGES.txt' - '*.md' - '*.svg' - .github/workflows/testwindows.yml @@ -33,7 +35,7 @@ jobs: PUBLISH_COVERAGE: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix @@ -65,7 +67,7 @@ jobs: BRANCH: badges - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} @@ -91,7 +93,9 @@ jobs: - name: Run tests run: | - BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data ./build/tests/bag_tests_d -r junit | tee build/tests/bag_tests-testreport.xml + export BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data + export BAG_HOME=${{github.workspace}}/configdata + ./build/tests/bag_tests_d -r junit | tee build/tests/bag_tests-testreport.xml - name: Test Reporter uses: mikepenz/action-junit-report@v5 @@ -102,7 +106,9 @@ jobs: - name: Run test coverage # Execute C++ tests run: | - BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data ninja -C build ccov-all-export-lcov + export BAG_SAMPLES_PATH=${{github.workspace}}/examples/sample-data + export BAG_HOME=${{github.workspace}}/configdata + ninja -C build ccov-bag_tests - name: Test coverage reporter uses: zgosalvez/github-actions-report-lcov@v4 @@ -118,13 +124,13 @@ jobs: if: ${{ env.PUBLISH_COVERAGE == 'true' }} run: | source python-venv/bin/activate - llvm-cov report -instr-profile=build/ccov/all-merged.profdata `cat ./build/ccov/binaries.list` -ignore-filename-regex="tests/*" -ignore-filename-regex="bag.cpp" | tail -n 1 | awk '{ print $10 }' | xargs python ./scripts/coverage-badge.py -o /tmp/coverage.svg -c + llvm-cov report -instr-profile=build/ccov/all-merged.profdata `cat ./build/ccov/binaries.list` -ignore-filename-regex="tests/*" | tail -n 1 | awk '{ print $10 }' | xargs python ./scripts/coverage-badge.py -o /tmp/coverage.svg -c deactivate - name: Checkout badges branch if: ${{ env.PUBLISH_COVERAGE == 'true' }} # Checkout badges branch of repo so that we can commit the coverage badge there - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ steps.coverage.outputs.branch }} diff --git a/.github/workflows/testwindows.yml b/.github/workflows/testwindows.yml index 9781cab59a..ccfa1c1805 100644 --- a/.github/workflows/testwindows.yml +++ b/.github/workflows/testwindows.yml @@ -6,6 +6,7 @@ on: - 'docs/**' - readme.* - README.* + - 'CHANGES.txt' - '*.md' - '*.svg' - '*.png' @@ -17,6 +18,7 @@ on: - 'docs/**' - readme.* - README.* + - 'CHANGES.txt' - '*.md' - '*.svg' - '*.png' @@ -38,17 +40,17 @@ jobs: fail-fast: true matrix: os: [windows-latest] - python-version: ["3.12"] + python-version: ["3.14"] c_compiler: [cl] include: - - VS_VERSION: Visual Studio 17 - VS_VER: 2022 + - VS_VERSION: Visual Studio 18 + VS_VER: 2026 SDK: release-1911 - MSVC_VER: 1920 + MSVC_VER: 1950 ZLIB_URL: "https://github.com/madler/zlib/releases/download/v1.3/zlib13.zip" - LIBXML2_URL: "https://download.gnome.org/sources/libxml2/2.13/libxml2-2.13.9.tar.xz" + LIBXML2_URL: "https://download.gnome.org/sources/libxml2/2.15/libxml2-2.15.1.tar.xz" HDF5_URL: "https://github.com/HDFGroup/hdf5/archive/refs/tags/hdf5_1.14.5.zip" - CATCH2_URL: "https://github.com/catchorg/Catch2/archive/refs/tags/v3.4.0.zip" + CATCH2_URL: "https://github.com/catchorg/Catch2/archive/refs/tags/v3.16.0.zip" env: ARCHITECTURE: "amd64" @@ -65,15 +67,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set compiler environment shell: cmd run: | - if "%VS_VER%" == "2022" CALL "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=%ARCHITECTURE% + if "%VS_VER%" == "2026" CALL "C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=%ARCHITECTURE% echo PATH=%PATH%>> %GITHUB_ENV% echo INCLUDE=%INCLUDE%>> %GITHUB_ENV% echo LIB=%LIB%>> %GITHUB_ENV% @@ -113,22 +115,34 @@ jobs: mkdir src cd src # zlib + if ("C561D09347F674F0D72692E7C75D9898919326C532AAB7F8C07BB43B07EFEB38" -ne (Get-FileHash ..\downloads\$env:ZLIB_ZIP).Hash) { + Write-Error "Invalid checksum for $env:ZLIB_ZIP" + exit 1 + } exec { 7z x ..\downloads\$env:ZLIB_ZIP } cd zlib-1.3 if(-Not (Test-Path -Path build)) { mkdir build } $env:CMAKE_INSTALL_PREFIX="-DCMAKE_INSTALL_PREFIX=" + $env:BAG_INSTALL_PATH cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release - cmake --build build --config Release --target install -- /nologo /verbosity:minimal + cmake --build build -j $env:NUMBER_OF_PROCESSORS --config Release --target install -- /nologo /verbosity:minimal cd .. # libxml2 + if ("C008BAC08FD5C7B4A87F7B8A71F283FA581D80D80FF8D2EFD3B26224C39BC54C" -ne (Get-FileHash ..\downloads\$env:LIBXML2_ZIP).Hash) { + Write-Error "Invalid checksum for $env:LIBXML2_ZIP" + exit 1 + } exec { tar xf ..\downloads\$env:LIBXML2_ZIP } - cd libxml2-2.13.9 + cd libxml2-2.15.1 if(-Not (Test-Path -Path build)) { mkdir build } cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release ` -DLIBXML2_WITH_ZLIB=ON -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF - cmake --build build --config Release --target install -- /nologo /verbosity:minimal + cmake --build build -j $env:NUMBER_OF_PROCESSORS --config Release --target install -- /nologo /verbosity:minimal cd .. # HDF5 + if ("911CADF4FEDFA6E5844D853D31F9125C960E0C960907DF9F0AC9CE2C4521DB32" -ne (Get-FileHash ..\downloads\$env:HDF5_ZIP).Hash) { + Write-Error "Invalid checksum for $env:HDF5_ZIP" + exit 1 + } exec { 7z x ..\downloads\$env:HDF5_ZIP } cd hdf5-hdf5_1.14.5 if(-Not (Test-Path -Path build)) { mkdir build } @@ -136,21 +150,25 @@ jobs: -DHDF5_BUILD_CPP_LIB=ON -DHDF5_BUILD_TOOLS:BOOL=OFF ` -DBUILD_TESTING:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=ON ` -DHDF5_BUILD_HL_LIB:BOOL=ON -DHDF5_ENABLE_Z_LIB_SUPPORT:BOOL=ON - cmake --build build --config Release --target install -- /nologo /verbosity:minimal + cmake --build build -j $env:NUMBER_OF_PROCESSORS --config Release --target install -- /nologo /verbosity:minimal cd .. # Catch2 + if ("1E96CCA4CE3BFBF1F20EFFF50C3A16FCC818D4BBD65C2C51155F976EE2BD0B8A" -ne (Get-FileHash ..\downloads\$env:CATCH2_ZIP).Hash) { + Write-Error "Invalid checksum for $env:CATCH2_ZIP" + exit 1 + } exec { 7z x ..\downloads\$env:CATCH2_ZIP } - cd Catch2-3.4.0 + cd Catch2-3.16.0 if(-Not (Test-Path -Path build)) { mkdir build } cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release ` -DBUILD_TESTING:BOOL=OFF - cmake --build build --config Release --target install -- /nologo /verbosity:minimal + cmake --build build -j $env:NUMBER_OF_PROCESSORS --config Release --target install -- /nologo /verbosity:minimal cd ..\.. # Build and install BAG C++ API cmake -G $env:VS_VERSION -DCMAKE_BUILD_TYPE=Release -B build -S . ` $env:CMAKE_INSTALL_PREFIX -DBUILD_SHARED_LIBS=ON ` -DBAG_BUILD_TESTS:BOOL=ON -DBAG_CI=ON -DCMAKE_OBJECT_PATH_MAX=1024 - cmake --build build --config Release --target install + cmake --build build -j $env:NUMBER_OF_PROCESSORS --config Release --target install # Build and install bagPy Python wheel $activate_cmd="$env:VENV_PATH\Scripts\Activate.ps1" echo "Attempting to activate Python environment using script: $activate_cmd ..." @@ -165,6 +183,7 @@ jobs: $env:Path += ";$env:BAG_INSTALL_PATH\bin" echo $env:Path $env:BAG_SAMPLES_PATH="$env:GITHUB_WORKSPACE\examples\sample-data" + $env:BAG_HOME="$env:GITHUB_WORKSPACE\configdata" $test_path="$env:GITHUB_WORKSPACE\build\tests\Release\bag_tests.exe" & $test_path # Run Python tests diff --git a/CHANGES.txt b/CHANGES.txt index d6a1c974d1..1a95a9173c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,67 @@ +========================= +BAG v.2.0.6 Release Notes +========================= + +October 2026 + +## Bug fixes + + - C API: [Fix buffer overflow in bagLegacyToWkt()](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/73c0162762c6ae81ac4b70b90b5b4ad6f2f20618) + - [bag_metadatatypes: bagFreeMetadata: Fix memory leak where BagMetadata.characterSet was not being freed](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/104e8334edc01eabb7d3904bd382f3107ec29098) Addresses #147. + - BAG::SurfaceCorrections: [Fix bug where surface correction vertical datum was not being successfully written to BAG files](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/c75f67e6d18ea7c493dceb8ee9d85f909b07890a) + - BAG::SurfaceCorrectionsDescriptor::create(): [Fix bug in surface correction creation where descriptor would not have spacings set, resulting in NaN values being generated when calling SurfaceCorrections::readCorrectedRow()](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/2a8aeb2c6aa892256907bd1658bbfdae7c28b710#diff-13ebba3f004faa1a7e07d3d76f1881f618f4f0e99b3d90c91207384e1dd806faR175) + - BAG::CompoundDataType: [Fix bug in move constructor](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/dc66c60e7f2d1e1142e2a5e238b2279ba688a08d) + - C API: [Fix bug in bagGetGeorefMetadataLayerDefinition() where definition return pointer was not properly set](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/130f0f2a71ee33bb8dc98a964eb42b39ac230fe2) + - MSVC: [Fix compatibility issues with MSVC exposed when trying to link to certain parts of the public API from unit tests](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/c2c2b53e5a9d90482c34fa45bef590523d927ecf) + + +## API changes + +> Note: These API changes are small and focused on fixing obvious bugs. In general they should not break most existing applications. + + - C + - [C: Modify bagCreateFromBuffer to allow handle to be null, which it must be when creating a new file](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/685e23cdb0b969bd446de8eeb1c11f12e971900a) + - [Fix error in bagWrite() declaration in bag.h, adding const qualifier to data parameter, which was present in the definition in bag.cpp](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/436ada9581940f5ccf6860e1e562e9dc60c7b499) + - [Update bagGetErrorString() to include all members of enum BAG_ERRORS](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/62d9d5cb6f372be524983d78c6c25f4611ba9a21) + + - C++ + - BAG::Dataset: + - [Update Dataset::readDataset() to rethrow H5::FileIException since this is not a recoverable error](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/ff01b6b58bad102498bab5a1bf0772c9276552dd) + - [BAG::Dataset::readDataset(): move version number parsing earlier in function so that invalid version numbers can be caught earlier and reported via new exception InvalidBAGVersion](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/3c17ab7cc8ebdd197707a3515f405b02a1cb7bf9) + - [Remove unused private function BAG::Dataset.readStringAttributeFromDataSet()](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/c6733875f46364470b5484f16492b34068db94cc) + - BAG::AttributeInfo: [Add default, copy, move, and move assignment constructors to facilitate unit testing](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/46c78d5a18da4f6a609281b32c2e3b37846d8d70) + - bag_legacy_crs.cpp‎: [Remove unused private functions not exported as part of the public API](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/3798f15d544557791d0563f0af142a3fb10c322b) + - [Refactor private utility functions getValue() into bag_util.cpp to facilitate unit testing](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/a948a6406b8ba7b42a738ac63b40dcf7e25b4d7e) + +## CMake + + - [Compile API and tests with `-fstack-protector-all` on GCC and Clang](https://github.com/OpenNavigationSurface/BAG/pull/150/changes/73c0162762c6ae81ac4b70b90b5b4ad6f2f20618) + - Update to new version of code-coverage.cmake + +## Testing updates + + - Greatly improved test coverage previous (from ~70% of functions to ca. 90%), with much higher line and branch coverage + - Update coverage configuration to enable branch coverage + - Begin adding tests for parts of the C API. + - Update from Catch2 v3.4.0 to v3.16.0, which should improve CI performance + - Update deprecated actions: checkout@4/@6 to @7, setup-python@4/@6 to @7. + +## CI + + - Dependencies + - Update from SWIG 4.3.0 to SWIG 4.4.1 + - Update libxml2 from version 2.13.9 to 2.15.1 + - Windows + - Calculate SHA256 sums for dependencies before unarchiving and building + - Enable parallel builds, which should improve CI performance + - Test matrix + - Windows + - Update from Visual Studio 2022 to 2026 + - Linux + - Update from Ubuntu 22.04 to 24.04 + - Python + - Update from CPython 3.10-3.13 to 3.11-3.14 + ========================= BAG v.2.0.5 Release Notes ========================= diff --git a/CMakeLists.txt b/CMakeLists.txt index f144ff3146..e5d12dee71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # CMake 3.15+ needed for SWIG version 4.0+ support. 3.18+ needed for Development.Module # sub-component support for FindPyton. cmake_minimum_required(VERSION 3.18) -project(OpenNavSurf-BAG LANGUAGES C CXX VERSION 2.0.5) +project(OpenNavSurf-BAG LANGUAGES C CXX VERSION 2.0.6) set(CMAKE_CXX_STANDARD 14) # SWIG: use standard target name. @@ -61,8 +61,10 @@ endif() if(BAG_CODE_COVERAGE) set(CODE_COVERAGE ON CACHE INTERNAL "Turn on code coverage") include(CMakeModules/code-coverage.cmake) - # Exclude C wrapper for now since our tests only target C++ code currently - add_code_coverage_all_targets(EXCLUDE test/* api/bag.cpp api/bag.h) + add_code_coverage_all_targets(EXCLUDE test/* + LCOV_OPTIONS --rc branch_coverage=1 + GENHTML_OPTIONS --branch-coverage + ) endif() # Generate setup.cfg and setup.py in case Python wheel is later generated diff --git a/CMakeModules/code-coverage.cmake b/CMakeModules/code-coverage.cmake index aac4b2f7f6..3227616edc 100644 --- a/CMakeModules/code-coverage.cmake +++ b/CMakeModules/code-coverage.cmake @@ -1,5 +1,5 @@ # -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# Copyright (C) 2018-2025 by George Cave - gcave@stablecoder.ca # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of @@ -61,17 +61,36 @@ # # ~~~ # add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# target_code_coverage(theExe +# EXCLUDE non_covered.cpp +# LCOV_EXCLUDE test/* +# LLVM_EXCLUDE test/.*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. # ~~~ # # Example 3: Target added to the 'ccov' and 'ccov-all' targets # # ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# add_code_coverage_all_targets( +# LCOV_EXCLUDE test/* +# LLVM_EXCLUDE test/.*)# Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. # # add_executable(theExe main.cpp non_covered.cpp) # target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. # ~~~ +# +# Example 4: Hook all targets +# +# ~~~ +# set(CCOV_TARGETS_HOOK ON) # enable 'add_executable' and 'add_library' hooks +# set(CCOV_TARGETS_HOOK_ARGS ALL AUTO) # set default arguments for coverage +# +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) # ccov-theLib target will be add +# +# add_executable(theExe main.cpp) # ccov-theExe target will be add +# target_link_libraries(theExe PRIVATE theLib) +# ~~~ # Options option( @@ -79,6 +98,10 @@ option( "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" OFF) +option(CCOV_TARGETS_HOOK "Autocapture all new targets." OFF) + +option(CCOV_TARGETS_HOOK_ARGS "Default arguments for all hooked targets.") + # Programs find_program(LLVM_COV_PATH llvm-cov) find_program(LLVM_PROFDATA_PATH llvm-profdata) @@ -88,24 +111,40 @@ find_program(GENHTML_PATH genhtml) mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) # Variables +set(CMAKE_COVERAGE_DATA_DIRECTORY ${CMAKE_BINARY_DIR}/ccov-data) set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) # Common initialization/checks if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) set(CODE_COVERAGE_ADDED ON) # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) + file(MAKE_DIRECTORY ${CMAKE_COVERAGE_DATA_DIRECTORY}) + file(MAKE_DIRECTORY ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}) if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") + + # to create lists of linked objects to avoid multi-process file handle + # issues + file(MAKE_DIRECTORY ${CMAKE_COVERAGE_DATA_DIRECTORY}/objects) + # to create lists of raw profile data while avoiding multi-process file + # handle issues + file(MAKE_DIRECTORY ${CMAKE_COVERAGE_DATA_DIRECTORY}/profraw) + + if(CMAKE_C_COMPILER_ID MATCHES "AppleClang" OR CMAKE_CXX_COMPILER_ID + MATCHES "AppleClang") + # When on macOS and using the Apple-provided toolchain, use the + # XCode-provided llvm toolchain via `xcrun` + message( + STATUS + "Building with XCode-provided llvm code coverage tools (via `xcrun`)") + set(LLVM_COV_PATH xcrun llvm-cov) + set(LLVM_PROFDATA_PATH xcrun llvm-profdata) + else() + # Use the regular llvm toolchain + message(STATUS "Building with llvm code coverage tools") + endif() if(NOT LLVM_COV_PATH) message(FATAL_ERROR "llvm-cov not found! Aborting.") @@ -125,48 +164,13 @@ if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) endif() # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") + add_custom_target(ccov-clean) elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Messages message(STATUS "Building with lcov Code Coverage Tools") - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() if(NOT LCOV_PATH) message(FATAL_ERROR "lcov not found! Aborting...") endif() @@ -181,47 +185,103 @@ if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) else() message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") endif() + + if(CCOV_TARGETS_HOOK) + if(COMMAND _add_executable) + message( + FATAL_ERROR + "add_executable was already redefined. Only one redefinitions is allowed." + ) + endif() + macro(add_executable) + _add_executable(${ARGV}) + target_code_coverage(${ARGV0} ${CCOV_TARGETS_HOOK_ARGS}) + endmacro(add_executable) + + if(COMMAND _add_library) + message( + FATAL_ERROR + "add_library was already redefined. Only one redefinitions is allowed." + ) + endif() + macro(add_library) + _add_library(${ARGV}) + target_code_coverage(${ARGV0} ${CCOV_TARGETS_HOOK_ARGS}) + endmacro(add_library) + endif(CCOV_TARGETS_HOOK) + endif() # Adds code coverage instrumentation to a library, or instrumentation/targets # for an executable target. # ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# Targets added (executables only): +# ccov-run-${TARGET_NAME} : Re-runs the executable, collecting fresh coverage data +# ccov-html-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-html : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. (same as ccov-html-${TARGET_NAME}) +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. (same as ccov-html) +# +# LLVM-based coverage targets added (executables only): # ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. # ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. # ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. # -# Required: +# Required Parameters: # TARGET_NAME - Name of the target to generate code coverage for. -# Optional: +# +# Optional Parameters: # PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. # INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. # PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) # AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all-*' targets created by a prior call to `add_code_coverage_all_targets` Effective on executable targets. # EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory # COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# OBJECTS - For executables ONLY, if the provided targets are static or shared libraries, adds coverage information to the output +# PRE_ARGS - For executables ONLY, prefixes given arguments to the associated ccov-run-${TARGET_NAME} executable call ($ ccov-*) +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-run-${TARGET_NAME} executable call (ccov-* $) +# EXCLUDE - Excludes files of the patterns provided from coverage. Added to any LLVM/LCOV specified excludes. (These do not copy to the 'all' targets) +# +# Optional Parameters effective with the clang/LLVM backend: +# LLVM_EXCLUDE - Excludes files that match the provided patterns, LLVM excludes by regex patterns. +# LLVM_PROFDATA_OPTIONS - Options are passed verbatim to the `llvm-profdata` call that merges/processes raw profile data. (.profraw -> .profdata) +# LLVM_COV_SHOW_OPTIONS - Options are passed verbatim to the `llvm-cov show` call for the `ccov-show-${TARGET_NAME}` target. +# LLVM_COV_REPORT_OPTIONS - Options are passed verbatim to the `llvm-cov report` call for the `ccov-report-${TARGET_NAME}` target. +# LLVM_COV_EXPORT_OPTIONS - Options are passed verbatim to the `llvm-cov export` call for the `ccov-export-${TARGET_NAME}` target. +# LLVM_COV_HTML_OPTIONS - Options are passed verbatim to the `llvm-cov show -format="html"` call for the `ccov-html-${TARGET_NAME}`/`ccov-${TARGET_NAME}` targets. +# +# Optional Parameters effective with the GCC/lcov backend: +# LCOV_EXCLUDE - Excludes files that match the provided patterns. LCOV exclude by glob patterns. +# LCOV_OPTIONS - Options are passed verbatim to the `lcov` call when capturing/filtering capture data +# GENHTML_OPTIONS - Options are passed verbatim to the `genhtml` call when generating the HTML report from lcov data for the `ccov-html-${TARGET_NAME}`/`ccov-${TARGET_NAME}` targets. # ~~~ function(target_code_coverage TARGET_NAME) + if(NOT CODE_COVERAGE) + return() + endif() + # Argument parsing set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) + set(multi_value_keywords + # common + EXCLUDE + OBJECTS + PRE_ARGS + ARGS + # llvm + LLVM_EXCLUDE + LLVM_PROFDATA_OPTIONS + LLVM_COV_SHOW_OPTIONS + LLVM_COV_REPORT_OPTIONS + LLVM_COV_EXPORT_OPTIONS + LLVM_COV_HTML_OPTIONS + # lcov + LCOV_EXCLUDE + LCOV_OPTIONS + GENHTML_OPTIONS) cmake_parse_arguments( target_code_coverage "${options}" "${single_value_keywords}" "${multi_value_keywords}" ${ARGN}) @@ -247,438 +307,644 @@ function(target_code_coverage TARGET_NAME) set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) endif() - if(CODE_COVERAGE) + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options( + ${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs -ftest-coverage + $<$:-fno-elide-constructors> -fno-default-inline) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) - # Add code coverage instrumentation to the target's linker command + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() + # If there are static or shared objects to also work with, generate the + # string to add them here + foreach(LINK_OBJECT ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${LINK_OBJECT}) + get_target_property(LINK_OBJECT_TYPE ${LINK_OBJECT} TYPE) + if(${LINK_OBJECT_TYPE} STREQUAL "STATIC_LIBRARY" + OR ${LINK_OBJECT_TYPE} STREQUAL "SHARED_LIBRARY") + set(LINKED_OBJECTS ${LINKED_OBJECTS} + -object=$) endif() - endforeach() + endif() + endforeach() - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it + ccov-clean-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw) + else() add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + ccov-clean-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw) + endif() + add_dependencies(ccov-clean + ccov-clean-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Run the executable, generating raw profile data Make the run data + # available for further processing. + add_custom_command( + OUTPUT ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + COMMAND + ${CMAKE_COMMAND} -E env ${CMAKE_CROSSCOMPILING_EMULATOR} + ${target_code_coverage_PRE_ARGS} + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${LINKED_OBJECTS} > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/objects/${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/profraw/${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + DEPENDS ${TARGET_NAME}) + + # This is a copy of the above add_custom_command. + # + # Since add_custom_target items are always considered out-of-date, this + # can be used by the user to perform another coverage run, but only when + # invoked directly. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env ${CMAKE_CROSSCOMPILING_EMULATOR} + ${target_code_coverage_PRE_ARGS} + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${LINKED_OBJECTS} > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/objects/${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/profraw/${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + DEPENDS ${TARGET_NAME}) + + # As custom targets with COMMANDs are always considered out of date, we + # want the merged/all targets to depend on this, so that we don't + # necessarily re-run the executbale every time, only at a minimum to + # generate the file. If the user want to re-run targets, they can by + # explicitly invoking the ccov-run-TARGET targets. + # + # If the option for a hidden target were possible, this would be. + add_custom_target( + ccov-profraw-${target_code_coverage_COVERAGE_TARGET_NAME} + DEPENDS ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw) + + # Merge the generated profile data so llvm-cov can process it + if(DEFINED CMAKE_XCODE_BUILD_SYSTEM) + # Xcode does not support the same file being a dependency for multiple + # targets, so depend on the run target to always run instead + add_custom_command( + OUTPUT ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse + ${LLVM_PROFDATA_PATH} merge + ${target_code_coverage_LLVM_PROFDATA_OPTIONS} -sparse ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + else() + add_custom_command( + OUTPUT ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + ${LLVM_PROFDATA_PATH} merge + ${target_code_coverage_LLVM_PROFDATA_OPTIONS} -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw) + endif() - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + add_custom_target( + ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME} + DEPENDS ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata) - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + # Ignore regex only works on LLVM >= 7 + set(EXCLUDE_REGEX) + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + foreach(EXCLUDE_ITEM ${target_code_coverage_LLVM_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${LINKED_OBJECTS} ${EXCLUDE_REGEX} + ${target_code_coverage_LLVM_COV_SHOW_OPTIONS} + DEPENDS ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME}) - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${LINKED_OBJECTS} ${EXCLUDE_REGEX} + ${target_code_coverage_LLVM_COV_REPORT_OPTIONS} + DEPENDS ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME}) - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${LINKED_OBJECTS} ${EXCLUDE_REGEX} + ${target_code_coverage_LLVM_COV_EXPORT_OPTIONS} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Only generates HTML output of the coverage information for perusal + add_custom_target( + ccov-html-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${LINKED_OBJECTS} ${EXCLUDE_REGEX} + ${target_code_coverage_LLVM_COV_HTML_OPTIONS} + DEPENDS ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + DEPENDS ccov-html-${target_code_coverage_COVERAGE_TARGET_NAME}) - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_DATA_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() + # Generate exclusion string for use + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + foreach(EXCLUDE_ITEM ${target_code_coverage_LCOV_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() - # Generates HTML output of the coverage information for perusal + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + ccov-clean-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters) + else() + add_custom_target( + ccov-clean-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters) endif() + add_dependencies(ccov-clean + ccov-clean-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Run the executable, generating raw profile data Make the run data + # available for further processing. + add_custom_command( + OUTPUT ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run + COMMAND + ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} + $ ${target_code_coverage_ARGS} + COMMAND # add a dummy file to use as a dependency to indicate the target + # has been run and data collected + ${CMAKE_COMMAND} -E touch + ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run + DEPENDS ${TARGET_NAME}) + + # This is a copy of the above add_custom_command. + # + # Since add_custom_target items are always considered out-of-date, this + # can be used by the user to perform another coverage run, but only when + # invoked directly. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} + $ ${target_code_coverage_ARGS} + COMMAND # add a dummy file to use as a dependency to indicate the target + # has been run and data collected + ${CMAKE_COMMAND} -E touch + ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run + DEPENDS ${TARGET_NAME}) + + # As custom targets with COMMANDs are always considered out of date, we + # want the merged/all targets to depend on this, so that we don't + # necessarily re-run the executbale every time, only at a minimum to + # generate the file. If the user want to re-run targets, they can by + # explicitly invoking the ccov-run-TARGET targets. + # + # If the option for a hidden target were possible, this would be. + add_custom_target( + ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME} + DEPENDS ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run) add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT + OUTPUT ${COVERAGE_INFO} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} ${target_code_coverage_LCOV_OPTIONS} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ${target_code_coverage_COVERAGE_TARGET_NAME}.ccov-run) + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + DEPENDS ${COVERAGE_INFO}) + + # Only generates HTML output of the coverage information for perusal + add_custom_target( + ccov-html-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} ${target_code_coverage_GENHTML_OPTIONS} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + COMMAND + ${CMAKE_COMMAND} -E echo "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) + DEPENDS ${COVERAGE_INFO}) - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + DEPENDS ccov-html-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT TARGET ccov-html) + add_custom_target(ccov-html) endif() + add_dependencies(ccov-html + ccov-html-${target_code_coverage_COVERAGE_TARGET_NAME}) - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) endif() + add_dependencies( + ccov-report ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-run) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets' to create the 'ccov-all' target set." + ) + endif() + + add_dependencies(ccov-all-run + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_dependencies( + ccov-all-ran + ccov-profiledata-${target_code_coverage_COVERAGE_TARGET_NAME}) + else() + add_dependencies( + ccov-all-ran + ccov-profraw-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() endif() endfunction() # Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, +# any subdirectories. +# +# To add coverage instrumentation to only specific targets, or to add targets, # use `target_code_coverage`. +# +# @WARNING: Does not add targets to collect coverage data from executables, use +# `target_code_coverage` to do so. function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() + if(NOT CODE_COVERAGE) + return() + endif() + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options( + -fprofile-arcs -ftest-coverage + $<$:-fno-elide-constructors> -fno-default-inline) + link_libraries(gcov) endif() endfunction() -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). +# Adds several 'ccov-all-*' targets that operates runs all targets added via +# `target_code_coverage` with the `ALL` parameter and merges all the coverage +# data into a single large report instead of numerous smaller ones. # ~~~ -# Optional: +# Targets added: +# ccov-all-run : Re-runs all tagged executables, collecting fresh coverage data +# ccov-all-html : Generates an HTML report of all tagged executable coverage data merged into one +# ccov-all : Generates an HTML report of all tagged executable coverage data merged into one (same as ccov-all-html) +# +# LLVM-based coverage targets added: +# ccov-all-report : Generates an HTML report of all tagged executable coverage data merged into one and displays it in the CLI +# ccov-all-export : Exports coverage data in JSON format for use in CI environments or similar +# +# GCC-based coverage targets added: +# ccov-all-capture : Generates an all-merged.info file, for use with coverage dashboards (e.g. codecov.io, coveralls). +# +# Optional Parameters: # EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# +# Optional Parameters effective with the clang/LLVM backend: +# LLVM_EXCLUDE - Excludes files that match the provided patterns, LLVM excludes by regex patterns. +# LLVM_PROFDATA_OPTIONS - Options are passed verbatim to the `llvm-profdata` call that merges/processes raw profile data. (.profraw -> .profdata) +# LLVM_COV_REPORT_OPTIONS - Options are passed verbatim to the `llvm-cov report` call for the `ccov-report-all` target. +# LLVM_COV_EXPORT_OPTIONS - Options are passed verbatim to the `llvm-cov export` call for the `ccov-export-all` target. +# LLVM_COV_HTML_OPTIONS - Options are passed verbatim to the `llvm-cov show -format="html"` call for the `ccov-html-all`/`ccov-all` targets. +# +# Optional Parameters effective with the GCC/lcov backend: +# LCOV_EXCLUDE - Excludes files that match the provided patterns. LCOV exclude by glob patterns. +# LCOV_OPTIONS - Options are passed verbatim to the `lcov` call when capturing/filtering capture data +# GENHTML_OPTIONS - Options are passed verbatim to the `genhtml` call when generating the HTML report from lcov data for the `ccov-html-all`/`ccov-all` targets. # ~~~ function(add_code_coverage_all_targets) + if(NOT CODE_COVERAGE) + return() + endif() + # Argument parsing - set(multi_value_keywords EXCLUDE) + set(multi_value_keywords + # common + EXCLUDE + # llvm + LLVM_EXCLUDE + LLVM_PROFDATA_OPTIONS + LLVM_COV_REPORT_OPTIONS + LLVM_COV_EXPORT_OPTIONS + LLVM_COV_HTML_OPTIONS + # lcov + LCOV_EXCLUDE + LCOV_OPTIONS + GENHTML_OPTIONS) cmake_parse_arguments(add_code_coverage_all_targets "" "" "${multi_value_keywords}" ${ARGN}) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # invoke to re-run all coverage-instrumented executables + add_custom_target(ccov-all-run) - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() + # used to ensure profile data from all targets is available, without forcing a + # re-run of previously run ones + add_custom_target(ccov-all-ran) - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() + # Merge the profile data for all of the run targets + if(WIN32) + add_custom_command( + OUTPUT ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + COMMAND powershell cat ${CMAKE_COVERAGE_DATA_DIRECTORY}/objects/* > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list + COMMAND powershell cat ${CMAKE_COVERAGE_DATA_DIRECTORY}/profraw/* > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-profraw.list + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-profraw.list \; llvm-profdata.exe + merge ${add_code_coverage_all_targets_LLVM_PROFDATA_OPTIONS} -o + ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata -sparse $$FILELIST + DEPENDS ccov-all-ran) + else() + add_custom_command( + OUTPUT ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + COMMAND + ${CMAKE_COMMAND} -E cat ${CMAKE_COVERAGE_DATA_DIRECTORY}/objects/* > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list + COMMAND + ${CMAKE_COMMAND} -E cat ${CMAKE_COVERAGE_DATA_DIRECTORY}/profraw/* > + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-profraw.list + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata -sparse `cat + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-profraw.list` + DEPENDS ccov-all-ran) + endif() + add_custom_target( + ccov-all-profiledata + DEPENDS ${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata) - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it (json format) + # Regex exclude only available for LLVM >= 7 + set(EXCLUDE_REGEX) + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_LLVM_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list \; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + ${EXCLUDE_REGEX} + ${add_code_coverage_all_targets_LLVM_COV_REPORT_OPTIONS} + DEPENDS ccov-all-profiledata) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list` + -instr-profile=${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + ${EXCLUDE_REGEX} + ${add_code_coverage_all_targets_LLVM_COV_REPORT_OPTIONS} + DEPENDS ccov-all-profiledata) + endif() + + # Export coverage information so continuous integration tools (e.g. Jenkins) + # can consume it + if(WIN32) add_custom_target( ccov-all-export COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list \; llvm-cov.exe + export $$FILELIST + -instr-profile=${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + -format="text" ${EXCLUDE_REGEX} + ${add_code_coverage_all_targets_LLVM_COV_EXPORT_OPTIONS} > ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it (lcov format) + DEPENDS ccov-all-profiledata) + else() add_custom_target( - ccov-all-export-lcov + ccov-all-export COMMAND ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="lcov" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/lcov.info - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list` + -instr-profile=${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + -format="text" ${EXCLUDE_REGEX} + ${add_code_coverage_all_targets_LLVM_COV_EXPORT_OPTIONS} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-profiledata) + endif() - # Generates HTML output of all targets for perusal + # Generate HTML output of all added targets for perusal + if(WIN32) add_custom_target( ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list \; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + ${add_code_coverage_all_targets_LLVM_COV_HTML_OPTIONS} + DEPENDS ccov-all-profiledata) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_DATA_DIRECTORY}/all-objects.list` + -instr-profile=${CMAKE_COVERAGE_DATA_DIRECTORY}/ccov-all.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + ${add_code_coverage_all_targets_LLVM_COV_HTML_OPTIONS} + DEPENDS ccov-all-profiledata) + endif() + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/ccov-all.info") + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_LCOV_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND + ${LCOV_PATH} ${add_code_coverage_all_targets_LCOV_OPTIONS} + ${EXCLUDE_REGEX} --output-file ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) endif() + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-clean + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters) + else() + add_custom_target( + ccov-all-clean + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters) + endif() + add_dependencies(ccov-clean ccov-all-clean) + add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT + OUTPUT ${COVERAGE_INFO} + COMMAND + ${LCOV_PATH} ${add_code_coverage_all_targets_LCOV_OPTIONS} --directory + ${CMAKE_BINARY_DIR} --capture --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + COMMAND ${CMAKE_COMMAND} -E echo + "Generated coverage .info file at ${COVERAGE_INFO}" + DEPENDS ccov-all-ran) + add_custom_target(ccov-all-capture DEPENDS ${COVERAGE_INFO}) + + # Only generates HTML output of all targets for perusal + add_custom_target( + ccov-all-html + COMMAND + ${GENHTML_PATH} ${add_code_coverage_all_targets_GENHTML_OPTIONS} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged ${COVERAGE_INFO} -p + ${CMAKE_SOURCE_DIR} + COMMAND + ${CMAKE_COMMAND} -E echo "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) + DEPENDS ${COVERAGE_INFO}) + + # Generates HTML output of all targets for perusal + add_custom_target(ccov-all DEPENDS ccov-all-html) + endif() -endfunction() +endfunction() \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 30c7e2a026..a0d6b7af2a 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,8 +1,10 @@ FROM debian:trixie-slim AS builder RUN apt-get update && \ - apt-get install -y git build-essential cmake ninja-build openssh-client \ - swig python3-dev python3-doc python3-venv python3-pip doxygen \ + apt-get install -y git build-essential cmake ninja-build openssh-client swig doxygen lcov \ + python3-dev python3-doc python3-venv python3-pip python3-setuptools python3-setuptools-scm python3-wheel \ + python3-pytest python3-pytest-cov python3-pytest-xdist python3-numpy python3-cmake-build-extension \ + python3-gdal gdal-bin \ curl zip unzip tar \ libglib2.0-dev \ libutfcpp-dev \ @@ -34,6 +36,16 @@ RUN cd /tmp && \ -DLIBXML2_WITH_ZLIB=ON -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF && \ cmake --build build --target install +# Install newish version of Catch2 +RUN cd /tmp && \ + wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.16.0.tar.gz && \ + echo "0957cae5821b17ce07f0833aaa52b5137643a8382203221f363a8303c109af34 v3.16.0.tar.gz" > catch2.sum && \ + shasum -a 256 -c catch2.sum && \ + tar xf v3.16.0.tar.gz && \ + cd Catch2-3.16.0 && \ + cmake -B build -G Ninja -S . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DBUILD_TESTING:BOOL=OFF && \ + cmake --build build --target install + # Install HDF5 RUN cd /tmp && \ wget https://github.com/HDFGroup/hdf5/releases/download/hdf5_1.14.5/hdf5-1.14.5.tar.gz && \ @@ -46,38 +58,11 @@ RUN cd /tmp && \ -DHDF5_BUILD_HL_LIB:BOOL=ON -DHDF5_ENABLE_Z_LIB_SUPPORT:BOOL=ON && \ cmake --build build --target install -# Install Catch2 version 3 (Debian only packages version 2) -RUN cd /tmp && \ - wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.6.0.tar.gz && \ - echo "485932259a75c7c6b72d4b874242c489ea5155d17efa345eb8cc72159f49f356 v3.6.0.tar.gz" > catch2.sum && \ - shasum -a 256 -c catch2.sum && \ - tar xf v3.6.0.tar.gz && \ - cd Catch2-3.6.0 && \ - cmake -B build -G Ninja -S . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DBUILD_TESTING:BOOL=OFF && \ - cmake --build build --target install - -# Install GDAL -RUN cd /tmp && \ - wget https://github.com/OSGeo/gdal/releases/download/v3.9.3/gdal-3.9.3.tar.gz && \ - echo "f293d8ccc6b98f617db88f8593eae37f7e4b32d49a615b2cba5ced12c7bebdae gdal-3.9.3.tar.gz" > gdal.sum && \ - shasum -a 256 -c gdal.sum && \ - tar xf gdal-3.9.3.tar.gz && \ - cd gdal-3.9.3 && \ - cmake -B build -G Ninja -S . -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr \ - -DBUILD_APPS=OFF -DBUILD_TESTING=OFF -DGDAL_ENABLE_DRIVER_BAG=ON \ - -DGDAL_USE_PARQUET=OFF -DGDAL_USE_ARROW=OFF -DGDAL_USE_ARROWDATASET=OFF \ - -DGDAL_ENABLE_HDF5_GLOBAL_LOCK:BOOL=ON -DBUILD_PYTHON_BINDINGS:BOOL=OFF -DBUILD_JAVA_BINDINGS:BOOL=OFF \ - -DBUILD_CSHARP_BINDINGS:BOOL=OFF && \ - cmake --build build --target install - RUN ldconfig -# Create Python venv and install dependencies -RUN python3 -m pip install --break-system-packages setuptools 'setuptools-scm[toml]' wheel cmake-build-extension \ - unittest-xml-reporting pytest pytest-cov pytest-xdist numpy && \ - # Install GDAL after numpy is installed so that gdal_array bindings will be installed. - python3 -m pip install --break-system-packages 'GDAL==3.9.3' +# Install Python dependencies not available from Debian +RUN python3 -m pip install --break-system-packages unittest-xml-reporting -WORKDIR /tmp/bag +WORKDIR /tmp/BAG ENTRYPOINT ["/bin/bash", "-c"] diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt index 8ef8637268..ed4a4ad460 100644 --- a/api/CMakeLists.txt +++ b/api/CMakeLists.txt @@ -9,11 +9,11 @@ configure_file(${BAG_VERSION_H_IN} ${BAG_VERSION_H_OUT}) set(BAG_SOURCE_FILES bag.cpp bag_attributeinfo.cpp - bag_georefmetadatalayer.cpp - bag_georefmetadatalayerdescriptor.cpp bag_dataset.cpp bag_deleteh5dataset.cpp bag_descriptor.cpp + bag_georefmetadatalayer.cpp + bag_georefmetadatalayerdescriptor.cpp bag_hdfhelper.cpp bag_interleavedlegacylayer.cpp bag_interleavedlegacylayerdescriptor.cpp @@ -30,6 +30,7 @@ set(BAG_SOURCE_FILES bag_surfacecorrections.cpp bag_surfacecorrectionsdescriptor.cpp bag_trackinglist.cpp + bag_util.cpp bag_valuetable.cpp bag_vrmetadata.cpp bag_vrmetadatadescriptor.cpp @@ -50,8 +51,6 @@ set(BAG_HEADER_FILES bag_attributeinfo.h bag_c_types.h bag_compounddatatype.h - bag_georefmetadatalayer.h - bag_georefmetadatalayerdescriptor.h bag_config.h bag_dataset.h bag_deleteh5dataset.h @@ -59,6 +58,8 @@ set(BAG_HEADER_FILES bag_errors.h bag_exceptions.h bag_fordec.h + bag_georefmetadatalayer.h + bag_georefmetadatalayerdescriptor.h bag_hdfhelper.h bag_interleavedlegacylayer.h bag_interleavedlegacylayerdescriptor.h @@ -76,6 +77,11 @@ set(BAG_HEADER_FILES bag_surfacecorrections.h bag_surfacecorrectionsdescriptor.h bag_trackinglist.h + bag_types.h + bag_uint8array.h + bag_util.h + bag_valuetable.h + bag_version.h bag_vrmetadata.h bag_vrmetadatadescriptor.h bag_vrnode.h @@ -83,10 +89,6 @@ set(BAG_HEADER_FILES bag_vrrefinements.h bag_vrrefinementsdescriptor.h bag_vrtrackinglist.h - bag_types.h - bag_uint8array.h - bag_valuetable.h - bag_version.h ) source_group("Header Files" FILES ${BAG_HEADER_FILES} ${BAG_PRIVATE_HEADER_FILES}) @@ -129,6 +131,11 @@ target_compile_options(baglib $<$,$,$,19.14>>:/Zc:__cplusplus> ) +# GCC/Clang options +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(baglib PRIVATE -fstack-protector-all) +endif() + find_package(HDF5 COMPONENTS CXX REQUIRED) find_package(LibXml2 MODULE REQUIRED) diff --git a/api/bag.cpp b/api/bag.cpp index e2623a58d0..7f8f93882e 100644 --- a/api/bag.cpp +++ b/api/bag.cpp @@ -11,6 +11,7 @@ #include "bag_surfacecorrections.h" #include "bag_surfacecorrectionsdescriptor.h" #include "bag_trackinglist.h" +#include "bag_util.h" #include "bag_valuetable.h" #include "bag_vrmetadata.h" #include "bag_vrmetadatadescriptor.h" @@ -37,82 +38,6 @@ #pragma warning(pop) #endif - -namespace { - -//! Convert a BAG::CompoundDataType (C++) into a BagCompoundDataType (C). -/*! -\param field - The BAG::CompoundDataType. - -\return - The BagCompoundDataType created from \e field. -*/ -BagCompoundDataType getValue( - const BAG::CompoundDataType& field) -{ - BagCompoundDataType result{}; - - result.type = field.getType(); - - switch(result.type) - { - case DT_FLOAT32: - result.data.f = field.asFloat(); - break; - case DT_UINT32: - result.data.ui32 = field.asUInt32(); - break; - case DT_BOOLEAN: - result.data.b = field.asBool(); - break; - case DT_STRING: // Copy the string as it will go out of scope. - { - const char* const value = field.asString().c_str(); - const auto fieldLen = strlen(value) + 1; - result.data.c = new char[fieldLen]; - memcpy(result.data.c, value, fieldLen); - break; - } - default: - result.type = DT_UNKNOWN_DATA_TYPE; - break; - } - - return result; -} - -//! Convert a BagCompoundDataType (C) into a BAG::CompoundDataType (C++). -/*! -\param field - The BagCompoundDataType. - -\return - The BAG::CompoundDataType created from \e field. -*/ -BAG::CompoundDataType getValue( - const BagCompoundDataType& field) -{ - switch (field.type) - { - case DT_FLOAT32: - return BAG::CompoundDataType{field.data.f}; - case DT_UINT32: - return BAG::CompoundDataType{field.data.ui32}; - case DT_BOOLEAN: - return BAG::CompoundDataType{field.data.b}; - case DT_STRING: - { - const char* value = field.data.c; - return BAG::CompoundDataType{std::string{value}}; - } - default: - return {}; - } -} - -} // namespace - //! Open the specified BAG. /*! \param handle @@ -145,6 +70,9 @@ BagError bagFileOpen( auto pHandle = std::make_unique(); pHandle->dataset = BAG::Dataset::open(std::string{fileName}, accessMode); + if (pHandle->dataset == nullptr) { + return BAG_NO_FILE_FOUND; + } *handle = pHandle.release(); } @@ -237,7 +165,7 @@ BagError bagCreateFromFile( /*! \param handle A handle to the new BAG. - Cannot be NULL. + Must be NULL. \param fileName The BAG file name. Cannot be NULL. @@ -257,9 +185,6 @@ BagError bagCreateFromBuffer( uint8_t* metadataBuffer, uint32_t metadataBufferSize) { - if (!handle) - return BAG_INVALID_BAG_HANDLE; - if (!fileName || !metadataBuffer) return BAG_INVALID_FUNCTION_ARGUMENT; @@ -461,15 +386,12 @@ BagError bagGetMinMaxSimple( float* minValue, float* maxValue) { - if (!handle) - return BAG_INVALID_BAG_HANDLE; + if (!handle) return BAG_INVALID_BAG_HANDLE; - if (!minValue || !maxValue) - return BAG_INVALID_FUNCTION_ARGUMENT; + if (!minValue || !maxValue) return BAG_INVALID_FUNCTION_ARGUMENT; const auto layer = handle->dataset->getSimpleLayer(type); - if (!layer) - return 9997; // layer type not found + if (!layer) return BAG_LAYER_MISSING; std::tie(*minValue, *maxValue) = layer->getDescriptor()->getMinMax(); @@ -757,6 +679,7 @@ BagError bagGetErrorString( switch (code) { + // Base errors case BAG_SUCCESS: strncpy(str, "Bag returned a successful completion", MAX_STR-1); break; @@ -778,6 +701,28 @@ BagError bagGetErrorString( case BAG_INVALID_FUNCTION_ARGUMENT: strncpy(str, "Invalid function argument or illegal value passed to Bag", MAX_STR-1); break; + case BAG_LAYER_MISSING: + strncpy(str, "The specified layer is missing", MAX_STR-1); + break; + case BAG_SIMPLE_LAYER_MISSING: + strncpy(str, "The specified simple layer is missing", MAX_STR-1); + break; + case BAG_WRONG_DESCRIPTOR_FOUND: + strncpy(str, "The wrong type of descriptor found for this layer", MAX_STR-1); + break; + case BAG_INVALID_LAYER_TYPE: + strncpy(str, "Layer type larger than UNKNOWN_LAYER_TYPE encountered", MAX_STR-1); + break; + case BAG_UNSPECIFIED_ERROR: + strncpy(str, "An unspecified error has been encountered", MAX_STR-1); + break; + case BAG_SURFACE_CORRECTIONS_MISSING: + strncpy(str, "The surface corrections layer is missing", MAX_STR-1); + break; + case BAG_MORE_BAG_INSTANCES_PRESENT: + strncpy(str, "There are still instances of the BAG in memory", MAX_STR-1); + break; + // Metadata errors case BAG_METADTA_NO_HOME: strncpy(str, "The BAG_HOME environment variable must be set to the configdata directory of the openns distribution", MAX_STR-1); break; @@ -817,15 +762,15 @@ BagError bagGetErrorString( case BAG_METADTA_INSUFFICIENT_BUFFER: strncpy(str, "Metadata supplied buffer is not large enough to hold the extracted contents from XML", MAX_STR-1); break; - case BAG_METADTA_UNCRT_MISSING: - strncpy(str, "Metadata 'uncertaintyType' information is missing from the XML structure", MAX_STR-1); - break; case BAG_METADTA_INCOMPLETE_COVER: strncpy(str, "Metadata One or more elements of the requested coverage are missing from the XML file", MAX_STR-1); break; case BAG_METADTA_INVLID_DIMENSIONS: snprintf(str, MAX_STR, "Metadata The number of dimensions is incorrect (not equal to %d)", RANK); break; + case BAG_METADTA_UNCRT_MISSING: + strncpy(str, "Metadata 'uncertaintyType' information is missing from the XML structure", MAX_STR-1); + break; case BAG_METADTA_BUFFER_EXCEEDED: strncpy(str, "Metadata supplied buffer is too large to be stored in the internal array", MAX_STR-1); break; @@ -862,6 +807,7 @@ BagError bagGetErrorString( case BAG_METADTA_NOT_INITIALIZED: strncpy(str, "The metadata has not been initialized correctly", MAX_STR-1); break; + // HDF errors case BAG_NOT_HDF5_FILE: strncpy(str, "HDF Bag is not an HDF5 File", MAX_STR-1); break; @@ -946,6 +892,37 @@ BagError bagGetErrorString( case BAG_HDF_INVALID_COMPRESSION_LEVEL: strncpy(str, "HDF compression level not in acceptable range of 0 to 9", MAX_STR-1); break; + case BAG_HDF_WRITE_ATTRIBUTE_FAILURE: + strncpy(str, "HDF Unable to write to Attribute", MAX_STR-1); + break; + // Georef metadata errors + case BAG_GEOREF_METADATA_LAYER_MISSING: + strncpy(str, "Georef: The specified layer does not exist", MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_RECORD_NOT_FOUND: + strncpy(str, "Georef: Unable to find record", MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_FIELD_NOT_FOUND: + strncpy(str, "Georef: Unable to find field", MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_NO_VALUE_FOUND: + strncpy(str, "Georef: Unable to find the value", MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_INVALID_RECORD_DEFINITION: + strncpy(str, + "Georef: The provided record does not match the definition in the georeferenced metadata layer", + MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_NAME_MISSING: + strncpy(str, "Georef: A georeferenced metadata layer name is required", MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_EXISTS: + strncpy(str, "Georef: The specified layer already exists", MAX_STR-1); + break; + case BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN: + strncpy(str, "Georef: The specified metadata profile is unknown", MAX_STR-1); + break; + // Invalid error code case BAG_INVALID_ERROR_CODE: default: strncpy(str, "An undefined bagError code was encountered", MAX_STR-1); @@ -1108,6 +1085,25 @@ void bagFree(uint8_t* buffer) delete[] buffer; } +// Private function to decompose CSV files +std::vector parseCSVString(const std::string& input, char sep) +{ + std::vector ret; + size_t start = 0; + while (true) { + auto pos = input.find(sep, start); + if (pos == std::string::npos) + { + ret.emplace_back(input.substr(start)); + break; + } + + ret.emplace_back(input.substr(start, pos - start)); + start = pos + 1; + } + return ret; +} + // Surface Corrections //! Retrieve the specified vertical datum from the surface corrections. /*! @@ -1193,45 +1189,54 @@ BagError bagWriteCorrectorVerticalDatum( uint8_t corrector, const uint8_t* inDatum) { - if (!handle) + if (!handle) { return BAG_INVALID_BAG_HANDLE; + } - if (corrector < 1 || corrector > BAG_SURFACE_CORRECTOR_LIMIT) + if (corrector < 1 || corrector > BAG_SURFACE_CORRECTOR_LIMIT) { return BAG_INVALID_FUNCTION_ARGUMENT; + } - if (!inDatum) + if (!inDatum) { return BAG_INVALID_FUNCTION_ARGUMENT; + } auto layer = handle->dataset->getSurfaceCorrections(); - if (!layer) + if (!layer) { return BAG_SURFACE_CORRECTIONS_MISSING; + } auto pDescriptor = std::dynamic_pointer_cast( layer->getDescriptor()); + if (!pDescriptor) { + return BAG_SURFACE_CORRECTIONS_MISSING; + } - // Set/replace the specified datum. - std::vector datums; - datums.reserve(BAG_SURFACE_CORRECTOR_LIMIT); - - std::istringstream iss{pDescriptor->getVerticalDatums()}; - - while (iss) + // Vertical datums are stored as a comma-separated string in the BAG file attribute + // "vertical_datum_corrections/vertical_datum", so before adding/updating the datum + // for corrector N, we need to decompose the string into a vector of values + auto datums = parseCSVString(pDescriptor->getVerticalDatums(), + BAG_CORRECTOR_VERTICAL_DATUM_SEP); + if (datums.empty()) { - std::string datum; - std::getline(iss, datum, ','); - if (!iss) - break; + datums.emplace_back(); + } - datums.emplace_back(std::move(datum)); + // Check if datums is large enough to store the number of correctors + if (datums.size() < corrector) + { + datums.resize(corrector); } - datums[corrector-1] = reinterpret_cast(inDatum); + datums[corrector - 1] = std::string(reinterpret_cast(inDatum)); - std::string joinedDatums = std::accumulate(cbegin(datums), cend(datums), - std::string{}, [](std::string& dest, const std::string& datum) - { - return dest.empty() ? datum : dest + ',' + datum; - }); + // Now pack the new datum, along with any other datums for other correctors, into a new CSV value + std::string joinedDatums; + for (size_t i = 0; i < datums.size(); ++i) + { + if (!joinedDatums.empty()) joinedDatums += BAG_CORRECTOR_VERTICAL_DATUM_SEP; + joinedDatums += datums[i]; + } pDescriptor->setVerticalDatums(joinedDatums); @@ -1295,12 +1300,16 @@ BagError bagReadCorrectedLayer( uint32_t columnEnd = 0; std::tie(rowEnd, columnEnd) = descriptor.getDims(); - auto correctedData = corrections->readCorrected(rowStart, rowEnd - 1, - columnStart, columnEnd - 1, corrector, *layer); - - *data = reinterpret_cast(correctedData.release()); - - return BAG_SUCCESS; + try { + auto correctedData = corrections->readCorrected(rowStart, rowEnd - 1, + columnStart, columnEnd - 1, corrector, *layer); + *data = reinterpret_cast(correctedData.release()); + return BAG_SUCCESS; + } catch (BAG::UnsupportedSurfaceType&) { + return BAG_SURFACE_CORRECTIONS_MISSING; + } catch (std::exception&) { + return BAG_UNSPECIFIED_ERROR; + } } //! Read a corrected region from a simple layer. @@ -1339,26 +1348,27 @@ BagError bagReadCorrectedRegion( BAG_LAYER_TYPE type, float** data) { - if (!handle) - return BAG_INVALID_BAG_HANDLE; + if (!handle) return BAG_INVALID_BAG_HANDLE; - if (!data) - return BAG_INVALID_FUNCTION_ARGUMENT; + if (!data) return BAG_INVALID_FUNCTION_ARGUMENT; const auto corrections = handle->dataset->getSurfaceCorrections(); - if (!corrections) - return BAG_SURFACE_CORRECTIONS_MISSING; + if (!corrections) return BAG_SURFACE_CORRECTIONS_MISSING; const auto layer = handle->dataset->getSimpleLayer(type); - if (!layer) - return BAG_HDF_DATASET_OPEN_FAILURE; + if (!layer) return BAG_LAYER_MISSING; - auto correctedData = corrections->readCorrected(rowStart, colStart, rowEnd, + try { + auto correctedData = corrections->readCorrected(rowStart, colStart, rowEnd, colEnd, corrector, *layer); + *data = reinterpret_cast(correctedData.release()); + return BAG_SUCCESS; + } catch (const BAG::UnsupportedSurfaceType&) { + return BAG_SURFACE_CORRECTIONS_MISSING; + } catch (const std::exception&) { + return BAG_UNSPECIFIED_ERROR; + } - *data = reinterpret_cast(correctedData.release()); - - return BAG_SUCCESS; } //! Read a corrected row from a simple layer. @@ -1388,31 +1398,33 @@ BagError bagReadCorrectedRow( BAG_LAYER_TYPE type, float** data) { - if (!handle) - return BAG_INVALID_BAG_HANDLE; + if (!handle) return BAG_INVALID_BAG_HANDLE; - if (!data) - return BAG_INVALID_FUNCTION_ARGUMENT; + if (!data) return BAG_INVALID_FUNCTION_ARGUMENT; const auto corrections = handle->dataset->getSurfaceCorrections(); - if (!corrections) - return BAG_SURFACE_CORRECTIONS_MISSING; + if (!corrections) return BAG_SURFACE_CORRECTIONS_MISSING; + + if (type > UNKNOWN_LAYER_TYPE) return BAG_INVALID_LAYER_TYPE; const auto layer = handle->dataset->getSimpleLayer(type); - if (!layer) - return BAG_HDF_DATASET_OPEN_FAILURE; + if (!layer) return BAG_LAYER_MISSING; constexpr uint32_t columnStart = 0; const auto& descriptor = handle->dataset->getDescriptor(); const auto columnEnd = std::get<1>(descriptor.getDims()); - auto correctedData = corrections->readCorrectedRow(row, columnStart, - columnEnd, corrector, *layer); - - *data = reinterpret_cast(correctedData.release()); - - return BAG_SUCCESS; + try { + auto correctedData = corrections->readCorrectedRow(row, columnStart, + columnEnd, corrector, *layer); + *data = reinterpret_cast(correctedData.release()); + return BAG_SUCCESS; + } catch (const BAG::UnsupportedSurfaceType&) { + return BAG_SURFACE_CORRECTIONS_MISSING; + } catch (const std::exception&) { + return BAG_UNSPECIFIED_ERROR; + } } //! Read a corrected node from a simple layer. @@ -1445,26 +1457,28 @@ BagError bagReadCorrectedNode( BAG_LAYER_TYPE type, float** data) { - if (!handle) - return BAG_INVALID_BAG_HANDLE; + if (!handle) return BAG_INVALID_BAG_HANDLE; - if (!data) - return BAG_INVALID_FUNCTION_ARGUMENT; + if (!data) return BAG_INVALID_FUNCTION_ARGUMENT; const auto corrections = handle->dataset->getSurfaceCorrections(); - if (!corrections) - return BAG_SURFACE_CORRECTIONS_MISSING; + if (!corrections) return BAG_SURFACE_CORRECTIONS_MISSING; - const auto layer = handle->dataset->getSimpleLayer(type); - if (!layer) - return BAG_SIMPLE_LAYER_MISSING; - - auto correctedData = corrections->readCorrectedRow(row, column, - column, corrector, *layer); + if (type > UNKNOWN_LAYER_TYPE) return BAG_INVALID_LAYER_TYPE; - *data = reinterpret_cast(correctedData.release()); + const auto layer = handle->dataset->getSimpleLayer(type); + if (!layer) return BAG_SIMPLE_LAYER_MISSING; - return BAG_SUCCESS; + try { + auto correctedData = corrections->readCorrectedRow(row, column, + column, corrector, *layer); + *data = reinterpret_cast(correctedData.release()); + return BAG_SUCCESS; + } catch (BAG::UnsupportedSurfaceType&) { + return BAG_SURFACE_CORRECTIONS_MISSING; + } catch (std::exception&) { + return BAG_UNSPECIFIED_ERROR; + } } //! Retrieve the number of correctors. @@ -1744,7 +1758,7 @@ BagError bagReadTrackingListNode( *numItems = static_cast(results.size()); *items = new BAG::TrackingItem[*numItems]; - memcpy(*items, results.data(), *numItems); + memcpy(*items, results.data(), results.size() * sizeof(BAG::TrackingItem)); return BAG_SUCCESS; } @@ -1793,7 +1807,7 @@ BagError bagReadTrackingListCode( *numItems = static_cast(results.size()); *items = new BAG::TrackingItem[*numItems]; - memcpy(*items, results.data(), *numItems); + memcpy(*items, results.data(), results.size() * sizeof(BAG::TrackingItem)); return BAG_SUCCESS; } @@ -1842,7 +1856,7 @@ BagError bagReadTrackingListSeries( *numItems = static_cast(results.size()); *items = new BAG::TrackingItem[*numItems]; - memcpy(*items, results.data(), *numItems); + memcpy(*items, results.data(), results.size() * sizeof(BAG::TrackingItem)); return BAG_SUCCESS; } @@ -2137,6 +2151,12 @@ BAG_EXTERNAL BagError bagCreateMetadataProfileGeorefMetadataLayer(BagHandle* han compressionLevel, indexType); } + catch (const BAG::UknownMetadataProfile&) { + return BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN; + } + catch (const BAG::LayerNotFound&) { + return BAG_LAYER_MISSING; + } catch(const std::exception& /*e*/) { return BAG_HDF_CREATE_DATASET_FAILURE; @@ -2184,12 +2204,11 @@ BagError bagGetGeorefMetadataLayerDefinition( // Convert the RecordDefinition into a FieldDefinition*. *numFields = static_cast(recordDef.size()); - auto* pDef = *definition; - pDef = new FieldDefinition[*numFields]; + *definition = new FieldDefinition[*numFields]; uint32_t index = 0; for (const auto& def : recordDef) - pDef[index++] = def; + *definition[index++] = def; return BAG_SUCCESS; } @@ -2266,7 +2285,7 @@ BagError bagGetGeorefMetadataLayerRecords( { // Copy the field auto& outField = pRecord[fieldIndex++]; - outField = getValue(field); + outField = BAG::getValue(field); } } @@ -2320,7 +2339,7 @@ BagError bagGetGeorefMetadataLayerValueByName( const auto& val = georefMetadataLayer->getValueTable().getValue(recordIndex, fieldName); - *value = getValue(val); + *value = BAG::getValue(val); } catch(const BAG::ValueNotFound& /*e*/) { @@ -2537,7 +2556,7 @@ BagError bagAddGeorefMetadataLayerRecord( size_t index = 0; for (auto& field : rec) - field = getValue(record[index++]); + field = BAG::getValue(record[index++]); try { @@ -2607,7 +2626,7 @@ BagError bagAddGeorefMetadataLayerRecords( size_t fieldIndex = 0; for (auto& field : rec) - field = getValue(records[recordIndex][fieldIndex++]); + field = BAG::getValue(records[recordIndex][fieldIndex++]); ++recordIndex; } @@ -2671,7 +2690,7 @@ BagError bagGeorefMetadataLayerSetValueByName( return BAG_GEOREF_METADATA_LAYER_MISSING; // Convert BagCompoundDataType into a BAG::CompoundDataType. - const auto val = getValue(*value); + const auto val = BAG::getValue(*value); try { @@ -2735,7 +2754,7 @@ BagError bagGeorefMetadataLayerSetValueByIndex( return BAG_GEOREF_METADATA_LAYER_MISSING; // Convert BagCompoundDataType into a BAG::CompoundDataType. - const auto val = getValue(*value); + const auto val = BAG::getValue(*value); try { diff --git a/api/bag.h b/api/bag.h index 0facdf996e..11918f4122 100644 --- a/api/bag.h +++ b/api/bag.h @@ -71,7 +71,7 @@ BAG_EXTERNAL BagError bagGetSpacing(BagHandle* handle, double* rowSpacing, doubl BAG_EXTERNAL BagError bagGetNumLayers(BagHandle* handle, uint32_t* numLayers); BAG_EXTERNAL bool bagContainsLayer(BagHandle* handle, BAG_LAYER_TYPE type, const char* layerName, BagError* bagError); BAG_EXTERNAL BagError bagRead(BagHandle* handle, uint32_t rowStart, uint32_t colStart, uint32_t rowEnd, uint32_t colEnd, BAG_LAYER_TYPE type, const char* layerName, uint8_t** data, double* x, double* y); -BAG_EXTERNAL BagError bagWrite(BagHandle* handle, uint32_t rowStart, uint32_t colStart, uint32_t rowEnd, uint32_t colEnd, BAG_LAYER_TYPE type, const char* layerName, uint8_t* data); +BAG_EXTERNAL BagError bagWrite(BagHandle* handle, uint32_t rowStart, uint32_t colStart, uint32_t rowEnd, uint32_t colEnd, BAG_LAYER_TYPE type, const char* layerName, const uint8_t* data); /* Simple layer access */ BAG_EXTERNAL BagError bagGetMinMaxSimple(BagHandle* handle, BAG_LAYER_TYPE type, float* minValue, float* maxValue); diff --git a/api/bag_attributeinfo.h b/api/bag_attributeinfo.h index eeae983faf..b6b0942c50 100644 --- a/api/bag_attributeinfo.h +++ b/api/bag_attributeinfo.h @@ -5,6 +5,9 @@ #ifndef BAG_ATTRIBUTEINFO_H #define BAG_ATTRIBUTEINFO_H +#include + +#include "bag_config.h" #include "bag_types.h" @@ -19,6 +22,10 @@ namespace BAG { //! This structure contains simple layer attribute information. struct AttributeInfo { + AttributeInfo() + : h5type(::H5::PredType::NATIVE_FLOAT) + {}; + AttributeInfo(const char* inMinName, const char* inMaxName, const char* inPath, const ::H5::PredType& inH5type) : minName(inMinName) @@ -27,6 +34,20 @@ struct AttributeInfo , h5type(inH5type) {} + AttributeInfo(const AttributeInfo& other) = default; + AttributeInfo(AttributeInfo&& other) = default; + AttributeInfo& operator=(AttributeInfo&& other) noexcept + { + if (this != &other) + { + minName = other.minName; + maxName = other.maxName; + path = other.path; + h5type = other.h5type; + } + return *this; + }; + //! The minimum value attribute name. const char* minName = nullptr; //! The maximum value attribute name. @@ -34,11 +55,11 @@ struct AttributeInfo //! The HDF5 path to the attribute. const char* path = nullptr; //! The HDF5 type the attribute is. - const ::H5::PredType& h5type; + H5::PredType h5type; }; //! Retrieve the simple layer attribute information. -AttributeInfo getAttributeInfo(LayerType layerType); +BAG_API AttributeInfo getAttributeInfo(LayerType layerType); } // namespace BAG diff --git a/api/bag_c_types.h b/api/bag_c_types.h index 134618061d..7e340860d1 100644 --- a/api/bag_c_types.h +++ b/api/bag_c_types.h @@ -172,6 +172,8 @@ struct BagVerticalDatumCorrectionsGridded float z[BAG_SURFACE_CORRECTOR_LIMIT]; }; +#define BAG_CORRECTOR_VERTICAL_DATUM_SEP ',' + //! The attributes of a correction surface. Only used in the C interface. struct BagVerticalCorrectorDef { @@ -200,11 +202,11 @@ typedef union { bool b; char* c; } BAG_COMPOUND_DATA_PAYLOAD; -struct BagCompoundDataType +typedef struct BagCompoundDataType { BAG_DATA_TYPE type; BAG_COMPOUND_DATA_PAYLOAD data; -}; +} BAG_COMPOUND_DATA_TYPE; #define BAG_NULL_ELEVATION 1000000 // A "null" elevation value. #define BAG_NULL_GENERIC 1000000 // A "null" generic value. diff --git a/api/bag_compounddatatype.h b/api/bag_compounddatatype.h index 5dd06f0896..8607fb67d4 100644 --- a/api/bag_compounddatatype.h +++ b/api/bag_compounddatatype.h @@ -11,12 +11,18 @@ namespace BAG { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) // std classes do not have DLL-interface when exporting +#pragma warning(disable: 4275) // non-DLL-interface class used as base class +#endif + //! This class is a tagged union (aka variant). /*! The supported types of this tagged union are: DT_FLOAT32, DT_UINT32, DT_BOOLEAN, DT_STRING */ -class CompoundDataType final { +class BAG_API CompoundDataType final { public: //! The default constructor. CompoundDataType() @@ -93,6 +99,7 @@ class CompoundDataType final { The object to be moved from. */ CompoundDataType(CompoundDataType&& other) + : type(other.type) { switch (type) { diff --git a/api/bag_dataset.cpp b/api/bag_dataset.cpp index 730d442f1c..4730e2472b 100644 --- a/api/bag_dataset.cpp +++ b/api/bag_dataset.cpp @@ -169,32 +169,6 @@ T readAttributeFromDataSet( return value; } -//! Helper to read a string attribute from an HDF5 DataSet. -/*! -\param h5file - The HDF5 file to read. -\param dataSetName - The name of the HDF5 DataSet. -\param attributeName - The name of the attribute. - -\return - The attribute value as a string. -*/ -std::string readStringAttributeFromDataSet( - const ::H5::H5File& h5file, - const std::string& dataSetName, - const std::string& attributeName) -{ - const ::H5::DataSet h5DataSet = h5file.openDataSet(dataSetName); - const ::H5::Attribute attribute = h5DataSet.openAttribute(attributeName); - - std::string value; - attribute.read(attribute.getDataType(), value); - - return value; -} - //! Helper to read a non-string attribute from an HDF5 Group. /*! \param h5file @@ -274,7 +248,9 @@ std::shared_ptr Dataset::open( pDataset->readDataset(fileName, openMode); } catch (H5::FileIException &fileExcept) { - std::cerr << "\nUnable to open BAG file: " << fileName << " due to error: " << fileExcept.getCDetailMsg(); + std::cerr << "\nUnable to open BAG file: " << fileName << " due to error: " << + fileExcept.getCDetailMsg() << std::endl; + fileExcept.printErrorStack(); return nullptr; } @@ -439,7 +415,7 @@ GeorefMetadataLayer& Dataset::createGeorefMetadataLayer( int compressionLevel, DataType keyType) & { - BAG::RecordDefinition definition = METADATA_DEFINITION_UNKNOWN; + RecordDefinition definition = METADATA_DEFINITION_UNKNOWN; try { definition = kGeorefMetadataProfileMapKnownRecordDefinition.at(profile); @@ -1105,15 +1081,22 @@ void Dataset::readDataset( catch( ::H5::FileIException& e ) { std::cerr << "Unable to read BAG file, error was: " << e.getCDetailMsg() << std::endl; - e.printErrorStack(); + throw; } m_pMetadata = std::make_unique(*this); m_descriptor = Descriptor{*m_pMetadata}; m_descriptor.setReadOnly(openMode == BAG_OPEN_READONLY); - m_descriptor.setVersion(readStringAttributeFromGroup(*m_pH5file, - ROOT_PATH, BAG_VERSION_NAME)); + + const auto verStr = readStringAttributeFromGroup(*m_pH5file, + ROOT_PATH, BAG_VERSION_NAME); + const auto bagVersion = getNumericalVersion(verStr); + if (bagVersion == 0) { + throw InvalidBAGVersion{}; + } + + m_descriptor.setVersion(verStr); const auto bagGroup = m_pH5file->openGroup(ROOT_PATH); @@ -1139,8 +1122,6 @@ void Dataset::readDataset( this->addLayer(SimpleLayer::open(*this, *layerDesc)); } - const auto bagVersion = getNumericalVersion(m_descriptor.getVersion()); - // If the BAG is version 1.5+ ... if (bagVersion >= 1'005'000) { diff --git a/api/bag_errors.h b/api/bag_errors.h index 1ccecb05b0..ac67c01a82 100644 --- a/api/bag_errors.h +++ b/api/bag_errors.h @@ -30,6 +30,8 @@ enum BAG_ERRORS { BAG_LAYER_MISSING = 8, //!< The specified layer is missing BAG_SIMPLE_LAYER_MISSING = 9, //!< The specified simple layer is missing BAG_WRONG_DESCRIPTOR_FOUND = 10, //!< The wrong type of descriptor found for this layer + BAG_INVALID_LAYER_TYPE = 11, //!< Layer type larger than UNKNOWN_LAYER_TYPE encountered + BAG_UNSPECIFIED_ERROR = 12, //!< Catch-all error, e.g., for when std::exception is caught. BAG_SURFACE_CORRECTIONS_MISSING = 20, //!< The surface corrections layer is missing @@ -94,13 +96,15 @@ enum BAG_ERRORS { BAG_HDF_INVALID_COMPRESSION_LEVEL = 631, //!< HDF compression level not in acceptable range of 0 to 9 BAG_HDF_WRITE_ATTRIBUTE_FAILURE = 632, //!< HDF Unable to write to Attribute - BAG_GEOREF_METADATA_LAYER_MISSING = BAG_GEOREF_METADATA_LAYER_ERROR_BASE, //!< The specified layer already exists - BAG_GEOREF_METADATA_LAYER_RECORD_NOT_FOUND = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 1, //!< Unable to find record. - BAG_GEOREF_METADATA_LAYER_FIELD_NOT_FOUND = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 2, //!< Unable to find field. - BAG_GEOREF_METADATA_LAYER_NO_VALUE_FOUND = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 3, //!< Unable to find the value. - BAG_GEOREF_METADATA_LAYER_INVALID_RECORD_DEFINITION = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 4, //!< The provided record does not match the definition in the georeferenced metadata layer. - BAG_GEOREF_METADATA_LAYER_NAME_MISSING = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 5, //!< A georeferenced metadata layer name is required - BAG_GEOREF_METADATA_LAYER_EXISTS = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 6, //!< The specified layer already exists + BAG_GEOREF_METADATA_LAYER_MISSING = BAG_GEOREF_METADATA_LAYER_ERROR_BASE, //!< Georef: The specified layer does not exist + BAG_GEOREF_METADATA_LAYER_RECORD_NOT_FOUND = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 1, //!< Georef: Unable to find record + BAG_GEOREF_METADATA_LAYER_FIELD_NOT_FOUND = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 2, //!< Georef: Unable to find field + BAG_GEOREF_METADATA_LAYER_NO_VALUE_FOUND = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 3, //!< Georef: Unable to find the value + BAG_GEOREF_METADATA_LAYER_INVALID_RECORD_DEFINITION = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 4, //!< Georef: The provided record does not match the definition in the georeferenced metadata layer + BAG_GEOREF_METADATA_LAYER_NAME_MISSING = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 5, //!< Georef: A georeferenced metadata layer name is required + BAG_GEOREF_METADATA_LAYER_EXISTS = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 6, //!< Georef: The specified layer already exists + BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 7, //!< Georef: The specified metadata profile is unknown + BAG_GEOREG_METADATA_LAYER_PROFILE_UNKNOWN = BAG_GEOREF_METADATA_LAYER_ERROR_BASE + 7, //!< Alias for BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN }; diff --git a/api/bag_exceptions.h b/api/bag_exceptions.h index 31abc59058..a8e8398b5e 100644 --- a/api/bag_exceptions.h +++ b/api/bag_exceptions.h @@ -86,6 +86,15 @@ struct BAG_API DatasetNotFound final : virtual std::exception } }; +//! Invalid Bag Version attribute. +struct BAG_API InvalidBAGVersion final : virtual std::exception +{ + const char* what() const noexcept override + { + return "Invalid BAG Version."; + } +}; + //! Invalid layer id. struct BAG_API InvalidLayerId final : virtual std::exception { diff --git a/api/bag_hdfhelper.cpp b/api/bag_hdfhelper.cpp index a9074f8fb2..b0bda56afe 100644 --- a/api/bag_hdfhelper.cpp +++ b/api/bag_hdfhelper.cpp @@ -398,7 +398,7 @@ void writeAttributes( The HDF5 path to the attribute in the HDF5 DataSet. Cannot be nullptr. */ -template void writeAttribute(const ::H5::DataSet& h5dataSet, +template BAG_API void writeAttribute(const ::H5::DataSet& h5dataSet, const ::H5::PredType& attributeType, float value, const char* path); //! Write an unsigned 32 bit integer value to an attribute. @@ -413,7 +413,7 @@ template void writeAttribute(const ::H5::DataSet& h5dataSet, The HDF5 path to the attribute in the HDF5 DataSet. Cannot be nullptr. */ -template void writeAttribute(const ::H5::DataSet& h5dataSet, +template BAG_API void writeAttribute(const ::H5::DataSet& h5dataSet, const ::H5::PredType& attributeType, uint32_t value, const char* path); //! Write a floating point value to multiple attributes. @@ -429,7 +429,7 @@ template void writeAttribute(const ::H5::DataSet& h5dataSet, If no paths provided, no attributes are written. If any path is nullptr, that attribute is not written. */ -template void writeAttributes(const ::H5::DataSet& h5dataSet, +template BAG_API void writeAttributes(const ::H5::DataSet& h5dataSet, const ::H5::PredType& attributeType, float value, const std::vector& paths); @@ -446,7 +446,7 @@ template void writeAttributes(const ::H5::DataSet& h5dataSet, If no paths provided, no attributes are written. If any path is nullptr, that attribute is not written. */ -template void writeAttributes(const ::H5::DataSet& h5dataSet, +template BAG_API void writeAttributes(const ::H5::DataSet& h5dataSet, const ::H5::PredType& attributeType, uint32_t value, const std::vector& paths); diff --git a/api/bag_hdfhelper.h b/api/bag_hdfhelper.h index 02a9873e1f..5cec84b110 100644 --- a/api/bag_hdfhelper.h +++ b/api/bag_hdfhelper.h @@ -3,7 +3,6 @@ #include "bag_georefmetadatalayer.h" #include "bag_config.h" -#include "bag_fordec.h" #include "bag_types.h" #include "bag_valuetable.h" @@ -26,29 +25,29 @@ class PredType; namespace BAG { -::H5::CompType createH5compType(LayerType layerType, +BAG_API ::H5::CompType createH5compType(LayerType layerType, GroupType groupType); -::H5::CompType createH5fileCompType(const RecordDefinition& definition); +BAG_API ::H5::CompType createH5fileCompType(const RecordDefinition& definition); -::H5::CompType createH5memoryCompType(const RecordDefinition& definition); +BAG_API ::H5::CompType createH5memoryCompType(const RecordDefinition& definition); -uint64_t getChunkSize(const ::H5::H5File& h5file, +BAG_API uint64_t getChunkSize(const ::H5::H5File& h5file, const std::string& path); -int getCompressionLevel(const ::H5::H5File& h5file, +BAG_API int getCompressionLevel(const ::H5::H5File& h5file, const std::string& path); -size_t getRecordSize(const RecordDefinition& definition); +BAG_API size_t getRecordSize(const RecordDefinition& definition); -const ::H5::AtomType& getH5fileType(DataType type); +BAG_API const ::H5::AtomType& getH5fileType(DataType type); -const ::H5::AtomType& getH5memoryType(DataType type); +BAG_API const ::H5::AtomType& getH5memoryType(DataType type); -::H5::Attribute createAttribute(const ::H5::DataSet& h5dataSet, +BAG_API ::H5::Attribute createAttribute(const ::H5::DataSet& h5dataSet, const ::H5::PredType& attributeType, const char* path); -void createAttributes(const ::H5::DataSet& h5dataSet, +BAG_API void createAttributes(const ::H5::DataSet& h5dataSet, const ::H5::PredType& attributeType, const std::vector& paths); template diff --git a/api/bag_legacy_crs.cpp b/api/bag_legacy_crs.cpp index c3a3bca8a7..41ec319202 100644 --- a/api/bag_legacy_crs.cpp +++ b/api/bag_legacy_crs.cpp @@ -94,33 +94,6 @@ std::vector split( return tokens; } -//************************************************************************ -/*! -\brief Convert the given string value to double. - -The string is expected to contain a decimal value in the classic locale. -For example something like "20.0". The classic locale uses a '.' as the -decimal separator. - -\param value - \li The string to convert. -\return - \li The converted value. -*/ -//************************************************************************ -double toDouble(const std::string& value) -{ - std::stringstream lineStream; - - (void)lineStream.imbue(std::locale::classic()); - lineStream << value; - - double dblValue = 0.0; - lineStream >> dblValue; - - return dblValue; -} - //************************************************************************ /*! \brief Convert the BAG ellipsoid to WKT. @@ -302,211 +275,6 @@ std::string datumToWkt( throw BAG::InvalidDatumError(); } -//************************************************************************ -/*! -\brief Retrieve the specified projection parameter. - -\param wkt - \li The wkt string containing the coordinate system definition. -\param paramName - \li The name of the projection parameter to retrieve. -\return - \li The specified projection parameter. -*/ -//************************************************************************ -double getProjectionParam( - const std::string& wkt, - const std::string& paramName) -{ - //Find the projection node in the wkt string. - const size_t startIndex = wkt.find(paramName); - if (startIndex == std::string::npos) - throw BAG::CoordSysError(); - - const size_t valueStartIndex = wkt.find(",", startIndex); - if (valueStartIndex == std::string::npos) - throw BAG::CoordSysError(); - - const size_t valueEndIndex = wkt.find("]", valueStartIndex); - if (valueEndIndex == std::string::npos) - throw BAG::CoordSysError(); - - //Extract the value - const size_t startPos = valueStartIndex + 1; - const size_t length = valueEndIndex - startPos; - const std::string value = wkt.substr(startPos, length); - - return toDouble(value); -} - -//************************************************************************ -/*! -\brief Retrieve the BAG coordinate type from the WKT definition. - -\param wkt - \li The wkt string containing the coordinate system definition. -\return - \li The BAG coordiante system type. -*/ -//************************************************************************ -BAG::CoordinateType getCoordinateType(const std::string& wkt) -{ - //Find the projection node in the wkt string. - const size_t startIndex = wkt.find("projection["); - - //If no projection node, then we must have a Geographic system. - if (startIndex == std::string::npos) - return BAG::CoordinateType::Geodetic; - - const size_t endIndex = wkt.find(R"("])", startIndex); - if (endIndex == std::string::npos) - throw BAG::CoordSysError(); - - //Extract the projection name. - const size_t startPos = startIndex + 12; - const size_t length = endIndex - startPos; - const std::string projName = wkt.substr(startPos, length); - - if (projName == k_albers_conic_equal_area) - return BAG::CoordinateType::Albers_Equal_Area_Conic; - else if (projName == k_azimuthal_equidistant) - return BAG::CoordinateType::Azimuthal_Equidistant; - else if (projName == k_bonne) - return BAG::CoordinateType::Bonne; - else if (projName == k_cassini_soldner) - return BAG::CoordinateType::Cassini; - else if (projName == k_cylindrical_equal_area) - return BAG::CoordinateType::Cylindrical_Equal_Area; - else if (projName == k_eckert_iv) - return BAG::CoordinateType::Eckert4; - else if (projName == k_eckert_vi) - return BAG::CoordinateType::Eckert6; - else if (projName == k_equirectangular) - return BAG::CoordinateType::Equidistant_Cylindrical; - else if (projName == k_gnomonic) - return BAG::CoordinateType::Gnomonic; - else if (projName == k_lambert_conformal_conic) - return BAG::CoordinateType::Lambert_Conformal_Conic; - else if (projName == k_mercator) - return BAG::CoordinateType::Mercator; - else if (projName == k_miller_cylindrical) - return BAG::CoordinateType::Miller_Cylindrical; - else if (projName == k_mollweide) - return BAG::CoordinateType::Mollweide; - else if (projName == k_new_zealand_map_grid) - return BAG::CoordinateType::NZMG; - else if (projName == k_orthographic) - return BAG::CoordinateType::Orthographic; - else if (projName == k_polar_stereographic) - return BAG::CoordinateType::Polar_Stereo; - else if (projName == k_polyconic) - return BAG::CoordinateType::Polyconic; - else if (projName == k_sinusoidal) - return BAG::CoordinateType::Sinusoidal; - else if (projName == k_oblique_stereographic) - return BAG::CoordinateType::Stereographic; - else if (projName == k_transverse_mercator) - return BAG::CoordinateType::Transverse_Mercator; - else if (projName == k_vandergrinten) - return BAG::CoordinateType::Van_der_Grinten; - - //No idea... - throw BAG::CoordSysError(); -} - -//************************************************************************ -/*! -\brief Retrieve the BAG datum type from the WKT definition. - -\param wkt - \li The wkt string containing the coordinate system definition. -\return - \li The BAG datum type. -*/ -//************************************************************************ -BAG::BagDatum getDatumType(const std::string &wkt) -{ - //Find the horizontal datum node in the wkt string. - const size_t startIndex = wkt.find("datum["); - if (startIndex == std::string::npos) - throw BAG::InvalidDatumError(); - - const size_t endIndex = wkt.find(",", startIndex); - if (endIndex == std::string::npos) - throw BAG::InvalidDatumError(); - - //Extract the horizontal datum name. - const size_t startPos = startIndex + 7; - const size_t length = endIndex - startPos - 1; - const std::string hDatumName = wkt.substr(startPos, length); - - if (hDatumName == k_wgs84) - return BAG::BagDatum::wgs84; - else if (hDatumName == k_wgs72) - return BAG::BagDatum::wgs72; - else if (hDatumName == k_nad83) - return BAG::BagDatum::nad83; - - //Unknown, so we can not convert this coordinate system. - throw BAG::InvalidDatumError(); -} - -//************************************************************************ -/*! -\brief Retrieve the BAG ellipsoid name from the WKT definition. - -\param wkt - \li The wkt string containing the coordinate system definition. -\return - \li The ellipsoid name. -*/ -//************************************************************************ -std::string getEllipsoid(const std::string& wkt) -{ - //Find the ellipsoid node in the wkt string. - const size_t startIndex = wkt.find("spheroid["); - if (startIndex == std::string::npos) - throw BAG::InvalidDatumError(); - - const size_t endIndex = wkt.find(",", startIndex); - if (endIndex == std::string::npos) - throw BAG::InvalidDatumError(); - - //Extract the ellipsoid name. - const size_t startPos = startIndex + 10; - const size_t length = endIndex - startPos - 1; - - return wkt.substr(startPos, length); -} - -//************************************************************************ -/*! -\brief Retrieve the vertical datum name from the WKT definition. - -\param wkt - \li The wkt string containing the vertical reference system definition. -\return - \li The vertical datum name. -*/ -//************************************************************************ -std::string getVDatum(const std::string& wkt) -{ - //Find the vertical datum node in the wkt string. - const size_t startIndex = wkt.find("vert_datum["); - if (startIndex == std::string::npos) - throw BAG::InvalidDatumError(); - - const size_t endIndex = wkt.find(",", startIndex); - if (endIndex == std::string::npos) - throw BAG::InvalidDatumError(); - - //Extract the vertical datum name. - const size_t startPos = startIndex + 12; - const size_t length = endIndex - startPos - 1; - - return wkt.substr(startPos, length); -} - } // namespace namespace BAG { @@ -525,12 +293,12 @@ namespace BAG { \li The projection parameters. \param hBuffer \li Modified to contain the horizontal reference system -in the form of a WKT string. +in the form of a WKT string. Will be truncated to hBufferSize-1. \param hBufferSize \li The size of the horizontal reference system buffer. \param vBuffer \li Modified to contain the vertical reference system -in the form of a WKT string. +in the form of a WKT string. Will be truncated to vBufferSize-1. \param vBufferSize \li The size of the vertical reference system buffer. \return @@ -556,11 +324,7 @@ try R"(", VERT_DATUM[")" << system.geoParameters.vertical_datum << R"(", 2000]])"; - //Make sure our string is not too large. - if (wktStream.str().size() > vBufferSize) - wktStream.str().resize(vBufferSize); - - strcpy(vBuffer, wktStream.str().c_str()); + strncpy(vBuffer, wktStream.str().c_str(), vBufferSize-1); } //If we want the horizontal system then... @@ -934,10 +698,7 @@ try } - if (wktStream.str().size() > hBufferSize) - wktStream.str().resize(hBufferSize); - - strcpy(hBuffer, wktStream.str().c_str()); + strncpy(hBuffer, wktStream.str().c_str(), hBufferSize-1); } return 0; diff --git a/api/bag_legacy_crs.h b/api/bag_legacy_crs.h index 260f8744e9..7e20b59c73 100644 --- a/api/bag_legacy_crs.h +++ b/api/bag_legacy_crs.h @@ -4,7 +4,7 @@ #include "bag_config.h" #include "bag_c_types.h" -#include +#include namespace BAG { @@ -85,10 +85,10 @@ struct BagLegacyReferenceSystem BagProjectionParameters geoParameters; //!< Parameters for projection information }; -CoordinateType bagCoordsys(const char* str) noexcept; -BagDatum bagDatumID(const char* str) noexcept; +BAG_API CoordinateType bagCoordsys(const char* str) noexcept; +BAG_API BagDatum bagDatumID(const char* str) noexcept; -BagError bagLegacyToWkt(const BagLegacyReferenceSystem& system, +BAG_API BagError bagLegacyToWkt(const BagLegacyReferenceSystem& system, char* hBuffer, size_t hBufferSize, char* vBuffer, size_t vBufferSize); } // namespace BAG diff --git a/api/bag_metadata_import.h b/api/bag_metadata_import.h index 9c400174d0..8fb68cf689 100644 --- a/api/bag_metadata_import.h +++ b/api/bag_metadata_import.h @@ -7,12 +7,13 @@ namespace BAG { -BagError bagImportMetadataFromXmlFile(const char* fileName, +BAG_API BagError bagImportMetadataFromXmlFile(const char* fileName, BagMetadata& metadata, bool doValidation); -BagError bagImportMetadataFromXmlBuffer(const char* xmlBuffer, int bufferSize, +BAG_API BagError bagImportMetadataFromXmlBuffer(const char* xmlBuffer, int bufferSize, BagMetadata& metadata, bool doValidation); -void bagSetHomeFolder(const char* homeFolder); +BAG_API void bagSetHomeFolder(const char* homeFolder); +BAG_API std::string bagGetHomeFolder(); } // namespace BAG diff --git a/api/bag_metadatatypes.cpp b/api/bag_metadatatypes.cpp index c893e89131..6817217702 100644 --- a/api/bag_metadatatypes.cpp +++ b/api/bag_metadatatypes.cpp @@ -63,9 +63,13 @@ void initResponsibleParty(BagResponsibleParty& responsibleParty) noexcept void freeResponsibleParty(BagResponsibleParty& responsibleParty) noexcept { delete[] responsibleParty.individualName; + responsibleParty.individualName = nullptr; delete[] responsibleParty.organisationName; + responsibleParty.organisationName = nullptr; delete[] responsibleParty.positionName; + responsibleParty.positionName = nullptr; delete[] responsibleParty.role; + responsibleParty.role = nullptr; } //************************************************************************ @@ -116,24 +120,38 @@ void initDataIdentificationInfo(BagIdentification& dataIdentificationInfo) noexc void freeDataIdentificationInfo(BagIdentification& dataIdentificationInfo) noexcept { delete[] dataIdentificationInfo.title; + dataIdentificationInfo.title = nullptr; delete[] dataIdentificationInfo.date; + dataIdentificationInfo.date = nullptr; delete[] dataIdentificationInfo.dateType; + dataIdentificationInfo.dateType = nullptr; delete[] dataIdentificationInfo.abstractString; + dataIdentificationInfo.abstractString = nullptr; delete[] dataIdentificationInfo.status; + dataIdentificationInfo.status = nullptr; delete[] dataIdentificationInfo.spatialRepresentationType; + dataIdentificationInfo.spatialRepresentationType = nullptr; delete[] dataIdentificationInfo.language; + dataIdentificationInfo.language = nullptr; delete[] dataIdentificationInfo.characterSet; + dataIdentificationInfo.characterSet = nullptr; delete[] dataIdentificationInfo.topicCategory; + dataIdentificationInfo.topicCategory = nullptr; delete[] dataIdentificationInfo.verticalUncertaintyType; + dataIdentificationInfo.verticalUncertaintyType = nullptr; delete[] dataIdentificationInfo.depthCorrectionType; + dataIdentificationInfo.depthCorrectionType = nullptr; delete[] dataIdentificationInfo.elevationSolutionGroupType; + dataIdentificationInfo.elevationSolutionGroupType = nullptr; delete[] dataIdentificationInfo.nodeGroupType; + dataIdentificationInfo.nodeGroupType = nullptr; for (uint32_t i = 0; i < dataIdentificationInfo.numberOfResponsibleParties; ++i) freeResponsibleParty(dataIdentificationInfo.responsibleParties[i]); delete[] dataIdentificationInfo.responsibleParties; + dataIdentificationInfo.responsibleParties = nullptr; } //************************************************************************ @@ -164,7 +182,9 @@ void initLegalConstraints(BagLegalConstraints& legalConstraints) noexcept void freeLegalConstraints(BagLegalConstraints& legalConstraints) noexcept { delete[] legalConstraints.useConstraints; + legalConstraints.useConstraints = nullptr; delete[] legalConstraints.otherConstraints; + legalConstraints.otherConstraints = nullptr; } //************************************************************************ @@ -195,7 +215,9 @@ void initSecurityConstraints(BagSecurityConstraints& securityConstraints) noexce void freeSecurityConstraints(BagSecurityConstraints& securityConstraints) noexcept { delete[] securityConstraints.classification; + securityConstraints.classification = nullptr; delete[] securityConstraints.userNote; + securityConstraints.userNote = nullptr; } //************************************************************************ @@ -231,14 +253,19 @@ void initSourceInfo(BagSource& sourceInfo) noexcept void freeSourceInfo(BagSource& sourceInfo) noexcept { delete[] sourceInfo.description; + sourceInfo.description = nullptr; delete[] sourceInfo.title; + sourceInfo.title = nullptr; delete[] sourceInfo.date; + sourceInfo.date = nullptr; delete[] sourceInfo.dateType; + sourceInfo.dateType = nullptr; for (uint32_t i = 0; i < sourceInfo.numberOfResponsibleParties; i++) freeResponsibleParty(sourceInfo.responsibleParties[i]); delete[] sourceInfo.responsibleParties; + sourceInfo.responsibleParties = nullptr; } //************************************************************************ @@ -276,18 +303,23 @@ void initProcessStep(BagProcessStep& processStep) noexcept void freeProcessStep(BagProcessStep& processStep) noexcept { delete[] processStep.description; + processStep.description = nullptr; delete[] processStep.dateTime; + processStep.dateTime = nullptr; delete[] processStep.trackingId; + processStep.trackingId = nullptr; for (uint32_t i = 0; i < processStep.numberOfSources; i++) freeSourceInfo(processStep.lineageSources[i]); delete[] processStep.lineageSources; + processStep.lineageSources = nullptr; for (uint32_t i = 0; i < processStep.numberOfProcessors; i++) freeResponsibleParty(processStep.processors[i]); delete[] processStep.processors; + processStep.processors = nullptr; } //************************************************************************ @@ -319,11 +351,13 @@ void initDataQualityInfo(BagDataQuality& dataQualityInfo) noexcept void freeDataQualityInfo(BagDataQuality& dataQualityInfo) noexcept { delete[] dataQualityInfo.scope; + dataQualityInfo.scope = nullptr; for (uint32_t i = 0; i < dataQualityInfo.numberOfProcessSteps; ++i) freeProcessStep(dataQualityInfo.lineageProcessSteps[i]); delete[] dataQualityInfo.lineageProcessSteps; + dataQualityInfo.lineageProcessSteps = nullptr; } //************************************************************************ @@ -369,9 +403,13 @@ void initSpatialRepresentationInfo(BagSpatialRepresentation& spatialRepresentati void freeSpatialRepresentationInfo(BagSpatialRepresentation& spatialRepresentationInfo) noexcept { delete[] spatialRepresentationInfo.resolutionUnit; + spatialRepresentationInfo.resolutionUnit = nullptr; delete[] spatialRepresentationInfo.cellGeometry; + spatialRepresentationInfo.cellGeometry = nullptr; delete[] spatialRepresentationInfo.transformationDimensionDescription; + spatialRepresentationInfo.transformationDimensionDescription = nullptr; delete[] spatialRepresentationInfo.transformationDimensionMapping; + spatialRepresentationInfo.transformationDimensionMapping = nullptr; } //************************************************************************ @@ -402,7 +440,9 @@ void initReferenceSystemInfo(BagReferenceSystem& referenceInfo) noexcept void freeReferenceSystemInfo(BagReferenceSystem& referenceInfo) noexcept { delete[] referenceInfo.definition; + referenceInfo.definition = nullptr; delete[] referenceInfo.type; + referenceInfo.type = nullptr; } //************************************************************************ @@ -463,53 +503,57 @@ void bagInitMetadata(BagMetadata& metadata) //************************************************************************ void bagFreeMetadata(BagMetadata& metadata) noexcept { - delete[] metadata.fileIdentifier; - delete[] metadata.dateStamp; + if (metadata.fileIdentifier) + { + delete[] metadata.fileIdentifier; + metadata.fileIdentifier = nullptr; + } + if (metadata.dateStamp) + { + delete[] metadata.dateStamp; + metadata.dateStamp = nullptr; + } delete[] metadata.language; + metadata.language = nullptr; + delete[] metadata.characterSet; + metadata.characterSet = nullptr; delete[] metadata.hierarchyLevel; + metadata.hierarchyLevel = nullptr; delete[] metadata.metadataStandardName; + metadata.metadataStandardName = nullptr; delete[] metadata.metadataStandardVersion; + metadata.metadataStandardVersion = nullptr; - if (metadata.contact) - freeResponsibleParty(*metadata.contact); - + if (metadata.contact) freeResponsibleParty(*metadata.contact); delete metadata.contact; + metadata.contact = nullptr; - if (metadata.spatialRepresentationInfo) - freeSpatialRepresentationInfo(*metadata.spatialRepresentationInfo); - + if (metadata.spatialRepresentationInfo) freeSpatialRepresentationInfo(*metadata.spatialRepresentationInfo); delete metadata.spatialRepresentationInfo; + metadata.spatialRepresentationInfo = nullptr; - if (metadata.horizontalReferenceSystem) - freeReferenceSystemInfo(*metadata.horizontalReferenceSystem); - + if (metadata.horizontalReferenceSystem) freeReferenceSystemInfo(*metadata.horizontalReferenceSystem); delete metadata.horizontalReferenceSystem; + metadata.horizontalReferenceSystem = nullptr; - if (metadata.verticalReferenceSystem) - freeReferenceSystemInfo(*metadata.verticalReferenceSystem); - + if (metadata.verticalReferenceSystem) freeReferenceSystemInfo(*metadata.verticalReferenceSystem); delete metadata.verticalReferenceSystem; + metadata.verticalReferenceSystem = nullptr; - - if (metadata.identificationInfo) - freeDataIdentificationInfo(*metadata.identificationInfo); - + if (metadata.identificationInfo) freeDataIdentificationInfo(*metadata.identificationInfo); delete metadata.identificationInfo; + metadata.identificationInfo = nullptr; - if (metadata.dataQualityInfo) - freeDataQualityInfo(*metadata.dataQualityInfo); - + if (metadata.dataQualityInfo) freeDataQualityInfo(*metadata.dataQualityInfo); delete metadata.dataQualityInfo; + metadata.dataQualityInfo = nullptr; - - if (metadata.legalConstraints) - freeLegalConstraints(*metadata.legalConstraints); - + if (metadata.legalConstraints) freeLegalConstraints(*metadata.legalConstraints); delete metadata.legalConstraints; + metadata.legalConstraints = nullptr; - if (metadata.securityConstraints) - freeSecurityConstraints(*metadata.securityConstraints); - + if (metadata.securityConstraints) freeSecurityConstraints(*metadata.securityConstraints); delete metadata.securityConstraints; + metadata.securityConstraints = nullptr; } diff --git a/api/bag_surfacecorrections.cpp b/api/bag_surfacecorrections.cpp index 10be24b4ac..18c4738094 100644 --- a/api/bag_surfacecorrections.cpp +++ b/api/bag_surfacecorrections.cpp @@ -10,6 +10,7 @@ #include // memset #include #include +#include namespace BAG { @@ -285,7 +286,9 @@ UInt8Array SurfaceCorrections::readCorrected( auto weakDataset = this->getDataset(); if (weakDataset.expired()) throw DatasetNotFound{}; - + // TODO: We can avoid the call to expired() and simply check if dataset evaluates to true after calling lock() + // As it stands now, we have a potential time-of-check vs. time-of-use bug. Let's fix this after we have + // a test covering this function. auto dataset = weakDataset.lock(); uint32_t ncols = 0, nrows = 0; @@ -366,6 +369,9 @@ UInt8Array SurfaceCorrections::readCorrectedRow( double nodeSpacingX = 0., nodeSpacingY = 0.; std::tie(nodeSpacingX, nodeSpacingY) = pDescriptor->getSpacing(); + if (nodeSpacingX == 0. || nodeSpacingY == 0.) { + throw InvalidValue{}; + } const auto resratio = nodeSpacingX / nodeSpacingY; @@ -377,7 +383,7 @@ UInt8Array SurfaceCorrections::readCorrectedRow( auto weakDataset = this->getDataset(); if (weakDataset.expired()) throw DatasetNotFound{}; - + // TODO: Just call lock and check dataset evaluates to true. auto dataset = weakDataset.lock(); double swCornerXsimple = 0., swCornerYsimple = 0.; @@ -594,9 +600,23 @@ void SurfaceCorrections::writeAttributesProxy() const // vertical datums auto tmpDatums = pDescriptor->getVerticalDatums(); if (tmpDatums.size() > kMaxDatumsLength) - tmpDatums.resize(kMaxDatumsLength); + tmpDatums.resize(kMaxDatumsLength-1); + + if (tmpDatums.size() > 0) + { + // Re-create vertical datum attribute if we have datum(s) to write + m_pH5dataSet->removeAttr(VERT_DATUM_CORR_VERTICAL_DATUM); + const hsize_t currLength = tmpDatums.size() + 1; + constexpr hsize_t kMaxSize = kMaxDatumsLength; + const ::H5::DataSpace kVerticalDatumDataSpace{1, &currLength, &kMaxSize}; + att = m_pH5dataSet->createAttribute(VERT_DATUM_CORR_VERTICAL_DATUM, ::H5::PredType::C_S1, + kVerticalDatumDataSpace); + } else + { + // Open existing attribute + att = m_pH5dataSet->openAttribute(VERT_DATUM_CORR_VERTICAL_DATUM); + } - att = m_pH5dataSet->openAttribute(VERT_DATUM_CORR_VERTICAL_DATUM); att.write(::H5::PredType::C_S1, tmpDatums); // Write any optional attributes. diff --git a/api/bag_surfacecorrectionsdescriptor.cpp b/api/bag_surfacecorrectionsdescriptor.cpp index 2039d299dd..8d4f9431c0 100644 --- a/api/bag_surfacecorrectionsdescriptor.cpp +++ b/api/bag_surfacecorrectionsdescriptor.cpp @@ -168,9 +168,13 @@ SurfaceCorrectionsDescriptor::create( if (numCorrectors > BAG_SURFACE_CORRECTOR_LIMIT) throw TooManyCorrections{}; - return std::shared_ptr( + auto ret = std::shared_ptr( new SurfaceCorrectionsDescriptor{dataset.getNextId(), type, numCorrectors, chunkSize, compressionLevel}); + auto gridSpacing = dataset.getDescriptor().getGridSpacing(); + ret->setSpacing(std::get<0>(gridSpacing), std::get<1>(gridSpacing)); + + return ret; } //! Open an existing surface corrections layer. diff --git a/api/bag_util.cpp b/api/bag_util.cpp new file mode 100644 index 0000000000..d3542377f3 --- /dev/null +++ b/api/bag_util.cpp @@ -0,0 +1,80 @@ +/*! +\file bag_util.cpp +\brief Internal utility functions +\copyright Open Navigation Surface Working Group, 2026. See LICENSE for more details. +*/ +#include "bag_config.h" +#include "bag_dataset.h" + +namespace BAG { + //! Convert a BAG::CompoundDataType (C++) into a BagCompoundDataType (C). + /*! + \param field + The CompoundDataType. + + \return + The BAG_COMPOUND_DATA_TYPE created from \e field. + */ + BAG_API BAG_COMPOUND_DATA_TYPE getValue( + const CompoundDataType& field) + { + BagCompoundDataType result{}; + + result.type = field.getType(); + + switch(result.type) + { + case DT_FLOAT32: + result.data.f = field.asFloat(); + break; + case DT_UINT32: + result.data.ui32 = field.asUInt32(); + break; + case DT_BOOLEAN: + result.data.b = field.asBool(); + break; + case DT_STRING: // Copy the string as it will go out of scope. + { + const char* const value = field.asString().c_str(); + const auto fieldLen = strlen(value) + 1; + result.data.c = new char[fieldLen]; + memcpy(result.data.c, value, fieldLen); + break; + } + default: + result.type = DT_UNKNOWN_DATA_TYPE; + break; + } + + return result; + } + + //! Convert a BagCompoundDataType (C) into a BAG::CompoundDataType (C++). + /*! + \param field + The BagCompoundDataType. + + \return + The CompoundDataType created from \e field. + */ + BAG_API CompoundDataType getValue( + const BAG_COMPOUND_DATA_TYPE field) + { + switch (field.type) + { + case DT_FLOAT32: + return CompoundDataType{field.data.f}; + case DT_UINT32: + return CompoundDataType{field.data.ui32}; + case DT_BOOLEAN: + return CompoundDataType{field.data.b}; + case DT_STRING: + { + const char* value = field.data.c; + return CompoundDataType{std::string{value}}; + } + default: + throw InvalidType{}; + } + } +} diff --git a/api/bag_util.h b/api/bag_util.h new file mode 100644 index 0000000000..7810248f62 --- /dev/null +++ b/api/bag_util.h @@ -0,0 +1,19 @@ +/*! +\file bag_util.h +\brief Internal utility functions +\copyright Open Navigation Surface Working Group, 2026. See LICENSE for more details. +*/ +#ifndef OPENNAVSURF_BAG_BAG_UTIL_H +#define OPENNAVSURF_BAG_BAG_UTIL_H + +#include "bag_compounddatatype.h" +#include "bag_c_types.h" + +/* Internal utility functions - expose here so that we can unit-test them. + * Note: Only expose "harmless" functions here. */ +namespace BAG { + BAG_COMPOUND_DATA_TYPE getValue(const CompoundDataType& field); + CompoundDataType getValue(BAG_COMPOUND_DATA_TYPE field); +} + +#endif //OPENNAVSURF_BAG_BAG_UTIL_H diff --git a/docs/BUILDING.md b/docs/BUILDING.md index ae2032f67b..5e26b40978 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -40,12 +40,22 @@ docker run -ti -v ./:/tmp/bag:rw dev/debian/baglib:latest /bin/bash ./scripts/dev-cont-build-bag.sh ``` +If building in an IDE, we recommend the following CMake flags: +``` +-G Ninja -DCMAKE_BUILD_TYPE=Debug -DBAG_BUILD_TESTS:BOOL=ON -DBAG_CODE_COVERAGE:BOOL=ON -DBAG_BUILD_PYTHON:BOOL=ON -DBAG_BUILD_EXAMPLES:BOOL=ON +``` + After running `dev-cont-build-bag.sh` once, you can iteratively run Python tests by running: ```shell python3 -m pytest python/test_*.py ``` +Note: If running tests manually via `./build/tests/bag_tests_d` and a test is throwing +an exception, you can see the details of where the exception is being thrown by running +the tests in a debugger, e.g.: `gdb ./build/tests/bag_tests_d` then typing `catch throw` +then `run`. + ## Linux / macOS ### Configure and build BAG @@ -104,7 +114,7 @@ $ ./bag-examples/examples/bag_georefmetadata_layer \ examples/sample-data/bag_georefmetadata_layer.xml bag_georefmetadata.bag ``` -## Windows: Visual Studio 2022/2019 +## Windows: Visual Studio 2026 ### Locally build dependencies @@ -112,8 +122,8 @@ First, from the directory `docs\win-build\baglibs\downloads` run `.\download.ps1 Then, from the directory `docs\win-build\baglibs\install` run `.\install.ps1` to build dependencies. -Download [swigwin-4.3.0](https://www.swig.org/download.html) and upzip to -`docs\win-build\baglibs\install` so that you have a directory named `swigwin-4.3.0`. +Download [swigwin-4.4.1](https://www.swig.org/download.html) and upzip to +`docs\win-build\baglibs\install` so that you have a directory named `swigwin-4.4.1`. Now, to build BAG, run the PowerShell script [win-build.ps1](../scripts/win-build.ps1). This will also run the C++ and Python tests. diff --git a/docs/win-build/baglibs/downloads/download.ps1 b/docs/win-build/baglibs/downloads/download.ps1 index 24b9e237f1..1d648d895e 100644 --- a/docs/win-build/baglibs/downloads/download.ps1 +++ b/docs/win-build/baglibs/downloads/download.ps1 @@ -1,4 +1,4 @@ Invoke-WebRequest https://github.com/madler/zlib/releases/download/v1.3/zlib13.zip -OutFile zlib.zip -Invoke-WebRequest https://gitlab.gnome.org/GNOME/libxml2/-/archive/v2.13.5/libxml2-v2.13.5.zip -OutFile libxml2.zip +Invoke-WebRequest https://download.gnome.org/sources/libxml2/2.15/libxml2-2.15.1.tar.xz -OutFile libxml2.xz Invoke-WebRequest https://github.com/HDFGroup/hdf5/archive/refs/tags/hdf5_1.14.5.zip -OutFile hdf5.zip -Invoke-WebRequest https://github.com/catchorg/Catch2/archive/refs/tags/v3.4.0.zip -OutFile catch2.zip +Invoke-WebRequest https://github.com/catchorg/Catch2/archive/refs/tags/v3.16.0.zip -OutFile catch2.zip diff --git a/docs/win-build/baglibs/install/install.ps1 b/docs/win-build/baglibs/install/install.ps1 index 0a3cfb7c2a..18d12be054 100644 --- a/docs/win-build/baglibs/install/install.ps1 +++ b/docs/win-build/baglibs/install/install.ps1 @@ -34,55 +34,71 @@ if ($null -eq $vsPath) { # Setup Visual Studio environment cmd.exe /c "$vsPath\Common7\Tools\VsDevCmd.bat" -arch=%ARCHITECTURE% $env:PATH += ";$vsPath\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin" -$env:VS_VERSION = "Visual Studio 17" +$env:VS_VERSION = "Visual Studio 18" # Project-specific variables $env:BAG_INSTALL_PATH=Resolve-Path '.\' $env:ZLIB_ZIP="zlib.zip" -$env:LIBXML2_ZIP="libxml2.zip" +$env:LIBXML2_ZIP="libxml2.xz" $env:HDF5_ZIP="hdf5.zip" $env:CATCH2_ZIP="catch2.zip" $env:BUILD_TYPE_CMAKE="Release" # zlib +if ("C561D09347F674F0D72692E7C75D9898919326C532AAB7F8C07BB43B07EFEB38" -ne (Get-FileHash ..\downloads\$env:ZLIB_ZIP).Hash) { + Write-Error "Invalid checksum for $env:ZLIB_ZIP" + exit 1 +} exec { 7z x ..\downloads\$env:ZLIB_ZIP } cd zlib-1.3 if(-Not (Test-Path -Path build)) { mkdir build } -$env:CMAKE_INSTALL_PREFIX="-DCMAKE_INSTALL_PREFIX=" + $env:BAG_INSTALL_PATH -cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=$env:BUILD_TYPE_CMAKE -cmake --build build --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal +$env:INSTALL_PREFIX="-DCMAKE_INSTALL_PREFIX=" + $env:BAG_INSTALL_PATH +cmake -B build -G $env:VS_VERSION -S . $env:INSTALL_PREFIX +cmake --build build -j $env:NUMBER_OF_PROCESSORS --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal cd .. -# libxml2 -exec { 7z x ..\downloads\$env:LIBXML2_ZIP } -cd libxml2-v2.13.5 +# libxml2 (Note: Debug build doesn't seem to work on Windows (BAG's CMake can't find it) so force Release build always) +if ("C008BAC08FD5C7B4A87F7B8A71F283FA581D80D80FF8D2EFD3B26224C39BC54C" -ne (Get-FileHash ..\downloads\$env:LIBXML2_ZIP).Hash) { + Write-Error "Invalid checksum for $env:LIBXML2_ZIP" + exit 1 +} +exec { tar xf ..\downloads\$env:LIBXML2_ZIP } +cd libxml2-2.15.1 if(-Not (Test-Path -Path build)) { mkdir build } -cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=$env:BUILD_TYPE_CMAKE ` +cmake -B build -G $env:VS_VERSION -S . $env:INSTALL_PREFIX ` -DLIBXML2_WITH_ZLIB=ON -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF -cmake --build build --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal +cmake --build build -j $env:NUMBER_OF_PROCESSORS --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal cd .. # HDF5 +if ("911CADF4FEDFA6E5844D853D31F9125C960E0C960907DF9F0AC9CE2C4521DB32" -ne (Get-FileHash ..\downloads\$env:HDF5_ZIP).Hash) { + Write-Error "Invalid checksum for $env:HDF5_ZIP" + exit 1 +} exec { 7z x ..\downloads\$env:HDF5_ZIP } cd hdf5-hdf5_1.14.5 if(-Not (Test-Path -Path build)) { mkdir build } -cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=$env:BUILD_TYPE_CMAKE ` --DHDF5_BUILD_CPP_LIB=ON -DHDF5_BUILD_TOOLS:BOOL=OFF ` +cmake -B build -G $env:VS_VERSION -S . $env:INSTALL_PREFIX ` +-DHDF5_BUILD_CPP_LIB=ON -DHDF5_BUILD_TOOLS:BOOL=ON ` -DBUILD_TESTING:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=ON ` -DHDF5_BUILD_HL_LIB:BOOL=ON -DHDF5_ENABLE_Z_LIB_SUPPORT:BOOL=ON -cmake --build build --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal +cmake --build build -j $env:NUMBER_OF_PROCESSORS --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal cd .. # Catch2 +if ("1E96CCA4CE3BFBF1F20EFFF50C3A16FCC818D4BBD65C2C51155F976EE2BD0B8A" -ne (Get-FileHash ..\downloads\$env:CATCH2_ZIP).Hash) { + Write-Error "Invalid checksum for $env:CATCH2_ZIP" + exit 1 +} exec { 7z x ..\downloads\$env:CATCH2_ZIP } -cd Catch2-3.4.0 +cd Catch2-3.16.0 if(-Not (Test-Path -Path build)) { mkdir build } # Debug: dir # Debug: pwd -cmake -B build -G $env:VS_VERSION -S . $env:CMAKE_INSTALL_PREFIX -DCMAKE_BUILD_TYPE=$env:BUILD_TYPE_CMAKE ` +cmake -B build -G $env:VS_VERSION -S . $env:INSTALL_PREFIX ` -DBUILD_TESTING:BOOL=OFF -cmake --build build --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal +cmake --build build -j $env:NUMBER_OF_PROCESSORS --config $env:BUILD_TYPE_CMAKE --target install -- /nologo /verbosity:minimal cd .. # Cleanup -rmdir -r .\Catch2-3.4.0 +rmdir -r .\Catch2-3.16.0 rmdir -r .\hdf5-hdf5_1.14.5 -rmdir -r .\libxml2-v2.13.5 +rmdir -r .\libxml2-2.15.1 rmdir -r .\zlib-1.3 diff --git a/examples/sample-data/invalid.xml b/examples/sample-data/invalid.xml new file mode 100644 index 0000000000..61dc8160ca --- /dev/null +++ b/examples/sample-data/invalid.xml @@ -0,0 +1 @@ +This is not an XML document. diff --git a/examples/sample-data/sample-bad-md.bag b/examples/sample-data/sample-bad-md.bag new file mode 100644 index 0000000000..62b37679b2 Binary files /dev/null and b/examples/sample-data/sample-bad-md.bag differ diff --git a/examples/sample-data/sample-bad-ver.bag b/examples/sample-data/sample-bad-ver.bag new file mode 100644 index 0000000000..f4b3cc02f9 Binary files /dev/null and b/examples/sample-data/sample-bad-ver.bag differ diff --git a/examples/sample-data/sample-no-md.bag b/examples/sample-data/sample-no-md.bag new file mode 100644 index 0000000000..32960c80d2 Binary files /dev/null and b/examples/sample-data/sample-no-md.bag differ diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000000..718199204e --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +unittest-xml-reporting diff --git a/scripts/dev-cont-build-bag.sh b/scripts/dev-cont-build-bag.sh index 504f27b523..70a17a4d8e 100755 --- a/scripts/dev-cont-build-bag.sh +++ b/scripts/dev-cont-build-bag.sh @@ -4,14 +4,16 @@ set -ex # Abort on error. # Note: This script is meant to be run within the development container defined by ../Dockerfile.dev. rm -rf venv-docker -python3 -m venv venv-docker +python3 -m venv --system-site-packages venv-docker source venv-docker/bin/activate -pip install -r requirements.txt -pip install mypy numpy GDAL==3.9.3 +pip install -r requirements-dev.txt +pip install mypy +# Create our own lcov config file to make sure branch coverage is always included +printf 'branch_coverage = 1\nno_exception_branch = 1\n' > ~/.lcovrc # Configure CMake cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -B build -S . \ - -DCMAKE_INSTALL_PREFIX=/usr -DBAG_BUILD_TESTS:BOOL=ON -DBAG_BUILD_EXAMPLES:BOOL=ON + -DCMAKE_INSTALL_PREFIX=/usr -DBAG_BUILD_EXAMPLES:BOOL=ON -DBAG_BUILD_TESTS:BOOL=ON -DBAG_CODE_COVERAGE:BOOL=ON # Build cmake --build build pip wheel -w ./wheel/ ./build/api/swig/python @@ -21,6 +23,8 @@ pip install --force-reinstall ./wheel/bagPy-*.whl # Generate PEP484 stub file stubgen -m bagPy -o ./python # Run tests -export BAG_SAMPLES_PATH=/tmp/bag/examples/sample-data -./build/tests/bag_tests_d +export BAG_HOME=/tmp/BAG/configdata +export BAG_SAMPLES_PATH=/tmp/BAG/examples/sample-data +# To run tests without coverage reporting, run: ./build/tests/bag_tests_d +ninja -C build ccov-bag_tests pytest python/test_*.py diff --git a/scripts/install-dependencies-cov.sh b/scripts/install-dependencies-cov.sh index 75ae22f480..f98b44c350 100644 --- a/scripts/install-dependencies-cov.sh +++ b/scripts/install-dependencies-cov.sh @@ -3,13 +3,13 @@ set -ex # Abort on error. pushd . sudo apt-get update -y sudo apt-get install -y llvm clang lcov cmake ninja-build libhdf5-dev libxml2-dev python3-venv -# Install Catch2 version 3 (Ubuntu 22.04 only packages version 2) +# Install a recent version of Catch2 version 3 cd /tmp -wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.4.0.tar.gz -echo "122928b814b75717316c71af69bd2b43387643ba076a6ec16e7882bfb2dfacbb v3.4.0.tar.gz" > catch2.sum +wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.16.0.tar.gz +echo "0957cae5821b17ce07f0833aaa52b5137643a8382203221f363a8303c109af34 v3.16.0.tar.gz" > catch2.sum shasum -a 256 -c catch2.sum -tar xf v3.4.0.tar.gz -cd Catch2-3.4.0 +tar xf v3.16.0.tar.gz +cd Catch2-3.16.0 cmake -B build -G Ninja -S . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DBUILD_TESTING:BOOL=OFF sudo cmake --build build --target install popd diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 98ea90f1f1..a7c086edcd 100644 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -7,24 +7,24 @@ echo "PYTHON_VERSION: ${PYTHON_VERSION}" pushd . sudo apt-get update -y -sudo apt-get install -y cmake g++ ninja-build swig4.0 zlib1g-dev libproj-dev -# Install Catch2 version 3 (Ubuntu 22.04 only packages version 2) +sudo apt-get install -y cmake g++ ninja-build swig zlib1g-dev libproj-dev +# Install a recent version of Catch2 version 3 cd /tmp -wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.4.0.tar.gz -echo "122928b814b75717316c71af69bd2b43387643ba076a6ec16e7882bfb2dfacbb v3.4.0.tar.gz" > catch2.sum +wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.16.0.tar.gz +echo "0957cae5821b17ce07f0833aaa52b5137643a8382203221f363a8303c109af34 v3.16.0.tar.gz" > catch2.sum shasum -a 256 -c catch2.sum -tar xf v3.4.0.tar.gz -cd Catch2-3.4.0 +tar xf v3.16.0.tar.gz +cd Catch2-3.16.0 cmake -B build -G Ninja -S . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DBUILD_TESTING:BOOL=OFF sudo cmake --build build --target install # Install libxml2 cd /tmp -wget https://download.gnome.org/sources/libxml2/2.13/libxml2-2.13.9.tar.xz -echo "a2c9ae7b770da34860050c309f903221c67830c86e4a7e760692b803df95143a libxml2-2.13.9.tar.xz" > libxml2.sum +wget https://download.gnome.org/sources/libxml2/2.15/libxml2-2.15.1.tar.xz +echo "c008bac08fd5c7b4a87f7b8a71f283fa581d80d80ff8d2efd3b26224c39bc54c libxml2-2.15.1.tar.xz" > libxml2.sum shasum -a 256 -c libxml2.sum -tar xf libxml2-2.13.9.tar.xz -cd libxml2-2.13.9 +tar xf libxml2-2.15.1.tar.xz +cd libxml2-2.15.1 cmake -B build -G Ninja -S . -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DLIBXML2_WITH_ZLIB=ON -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_PYTHON=OFF sudo cmake --build build --target install diff --git a/scripts/win-build.ps1 b/scripts/win-build.ps1 index b6305f9005..68bd260b71 100644 --- a/scripts/win-build.ps1 +++ b/scripts/win-build.ps1 @@ -23,20 +23,23 @@ pushd $content_root $env:CC="cl.exe" $env:CXX="cl.exe" -cmake -G "Visual Studio 17 2022" -B build -S . -DCMAKE_BUILD_TYPE=Release ` +$env:BUILD_TYPE_CMAKE="Release" +cmake -G "Visual Studio 18 2026" -B build -S . ` -DCMAKE_PREFIX_PATH=docs\win-build\baglibs\install ` -DCMAKE_INSTALL_PREFIX=docs\win-build\baglibs\install -DBUILD_SHARED_LIBS=ON ` -DBAG_BUILD_TESTS:BOOL=ON -DBAG_CI=ON -DCMAKE_OBJECT_PATH_MAX=1024 -cmake --build build --config Release --target install -$env:PATH=$env:PATH + ";${content_root}\build\api\Release;${content_root}\docs\win-build\baglibs\install\bin" + +cmake --build build -j $env:NUMBER_OF_PROCESSORS --config $env:BUILD_TYPE_CMAKE --target install +$env:PATH=$env:PATH + ";${content_root}\build\api\${env:BUILD_TYPE_CMAKE};${content_root}\docs\win-build\baglibs\install\bin" +$env:BAG_HOME="${content_root}\configdata" $env:BAG_SAMPLES_PATH="${content_root}\examples\sample-data" -build\tests\Release\bag_tests.exe -python -m venv win-venv +& ".\build\tests\${env:BUILD_TYPE_CMAKE}\bag_tests.exe" +py -m venv win-venv & .\win-venv\Scripts\Activate.ps1 python -m pip install --upgrade pip python -m pip install -r requirements.txt $env:CMAKE_PREFIX_PATH="$PWD\docs\win-build\baglibs\install" -$env:SWIG_EXECUTABLE="$PWD\docs\win-build\baglibs\install\swigwin-4.3.0\swig.exe" +$env:SWIG_EXECUTABLE="$PWD\docs\win-build\baglibs\install\swigwin-4.4.1\swig.exe" python -m pip wheel -w .\wheel\ .\build\api\swig\python $whl_path=Resolve-Path ".\wheel\bagPy-*.whl" python -m pip install $whl_path diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9bb7fcb85f..00ca3e49b2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,22 +7,30 @@ set(CMAKE_DEBUG_POSTFIX "d") set(TEST_SOURCE_FILES test_main.cpp + test_bag_attributeinfo.cpp + test_bag_c_api.cpp test_bag_compounddatatype.cpp test_bag_dataset.cpp test_bag_descriptor.cpp + test_bag_exceptions.cpp test_bag_georefmetadata_layer.cpp + test_bag_hdfhelper.cpp test_bag_interleavedlegacylayer.cpp test_bag_interleavedlegacylayerdescriptor.cpp + test_bag_legacy_crs.cpp test_bag_metadata.cpp + test_bag_metadata_export.cpp + test_bag_metadata_import.cpp + test_bag_metadatatypes.cpp test_bag_record.cpp test_bag_simplelayer.cpp test_bag_simplelayerdescriptor.cpp - test_bag_surfacecorrectionsdescriptor.cpp test_bag_surfacecorrections.cpp + test_bag_surfacecorrectionsdescriptor.cpp test_bag_trackinglist.cpp + test_bag_uint8array.cpp + test_bag_util.cpp test_bag_valuetable.cpp - test_utils.cpp - test_utils.h test_bag_vrmetadata.cpp test_bag_vrmetadatadescriptor.cpp test_bag_vrnode.cpp @@ -30,6 +38,8 @@ set(TEST_SOURCE_FILES test_bag_vrrefinements.cpp test_bag_vrrefinementsdescriptor.cpp test_bag_vrtrackinglist.cpp + test_utils.cpp + test_utils.h test_vr_bag.cpp ) @@ -62,6 +72,11 @@ target_compile_options(bag_tests $<$,$,$,19.14>>:/Zc:__cplusplus> ) +# GCC/Clang options +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(bag_tests PRIVATE -fstack-protector-strong) +endif() + find_package(HDF5 COMPONENTS CXX REQUIRED) find_package(Catch2 3 REQUIRED) @@ -85,5 +100,9 @@ include(CTest) include(Catch) if(BAG_CODE_COVERAGE) - target_code_coverage(bag_tests AUTO ALL EXCLUDE tests/*) + # We exclude bag_exceptions.h as it defines trivial classes the artificially depress coverage stats. + target_code_coverage(bag_tests AUTO ALL EXCLUDE api/bag_exceptions.h tests/* + LCOV_OPTIONS --rc branch_coverage=1 + GENHTML_OPTIONS --branch-coverage + ) endif() diff --git a/tests/test_bag_attributeinfo.cpp b/tests/test_bag_attributeinfo.cpp new file mode 100644 index 0000000000..0025c6d97f --- /dev/null +++ b/tests/test_bag_attributeinfo.cpp @@ -0,0 +1,72 @@ +#include "test_utils.h" + +#include +#include + +#include + +using LayerType = BAG_LAYER_TYPE; + +TEST_CASE("getAttributeInfo returns correct info for valid layer types", "[attributeInfo][valid]") { + BAG::AttributeInfo info; + // Test Elevation + info = BAG::getAttributeInfo(Elevation); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Uncertainty + info = BAG::getAttributeInfo(Uncertainty); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Hypothesis_Strength + info = BAG::getAttributeInfo(Hypothesis_Strength); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Num_Hypotheses + info = BAG::getAttributeInfo(Num_Hypotheses); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Shoal_Elevation + info = BAG::getAttributeInfo(Shoal_Elevation); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Std_Dev + info = BAG::getAttributeInfo(Std_Dev); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Num_Soundings + info = BAG::getAttributeInfo(Num_Soundings); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Average_Elevation + info = BAG::getAttributeInfo(Average_Elevation); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); + + // Test Nominal_Elevation + info = BAG::getAttributeInfo(Nominal_Elevation); + REQUIRE(info.minName != nullptr); + REQUIRE(info.maxName != nullptr); + REQUIRE(info.path != nullptr); +} + +TEST_CASE("getAttributeInfo throws UnsupportedSimpleLayerType for unknown layer type", "[attributeInfo][UnsupportedLayerType]") { + // We use an invalid LayerType value to trigger the default case in getAttributeInfo + auto invalidType = static_cast(100); + + REQUIRE_THROWS_AS(BAG::getAttributeInfo(invalidType), BAG::UnsupportedSimpleLayerType); +} diff --git a/tests/test_bag_c_api.cpp b/tests/test_bag_c_api.cpp new file mode 100644 index 0000000000..78bb7fc432 --- /dev/null +++ b/tests/test_bag_c_api.cpp @@ -0,0 +1,2691 @@ + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "test_utils.h" + +namespace +{ + const std::string kMetadataXML{R"( + + + Unique Identifier + + + eng + + + utf8 + + + dataset + + + + + Name of individual responsible for the BAG + + + pointOfContact + + + + + 2012-01-27 + + + ISO 19115 + + + 2003/Cor.1:2006 + + + + + 2 + + + + + row + + + 100 + + + 10 + + + + + + + column + + + 100 + + + 10 + + + + + point + + + 1 + + + 0 + + + + 687910.000000,5554620.000000 691590.000000,5562100.000000 + + + + center + + + + + + + + + PROJCS["UTM-19N-Nad83", + GEOGCS["unnamed", + DATUM["North_American_Datum_1983", + SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], + TOWGS84[0,0,0,0,0,0,0]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433], + EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], + EXTENSION["Source","CARIS"]], + PROJECTION["Transverse_Mercator"], + PARAMETER["latitude_of_origin",0], + PARAMETER["central_meridian",-69], + PARAMETER["scale_factor",0.9996], + PARAMETER["false_easting",500000], + PARAMETER["false_northing",0], + UNIT["metre",1]] + + + WKT + + + + + + + + + + + VERT_CS["Alicante height", + VERT_DATUM["Alicante",2000]] + + + WKT + + + + + + + + + + + Name of dataset input + + + + + 2008-10-21 + + + creation + + + + + + + Person responsible for input data + + + originator + + + + + + + Sample Metadata + + + completed + + + grid + + + eng + + + utf8 + + + elevation + + + + + + + -66.371629 + + + -66.316454 + + + 50.114053 + + + 50.180077 + + + + + + + rawStdDev + + + trueDepth + + + cube + + + product + + + + + + + + + dataset + + + + + + + + + List to be determined by WG. I.e. Product Creation + + + 2008-10-21T12:21:53 + + + + + Name of the processor + + + processor + + + + + + + Source + + + + + Name of dataset input + + + + + + creation + + + + + + + + + 1 + + + + + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + + +)"}; + + const std::string sampleBagPath() + { + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + return bagFileName; + } + + const std::string sampleMetadataPath() + { + const std::string metdataFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.xml"}; + return metdataFileName; + } + + BagHandle* openSampleBag(BAG_OPEN_MODE mode = BAG_OPEN_MODE::BAG_OPEN_READONLY) + { + BagHandle* handle = nullptr; + auto bagFile = sampleBagPath(); + REQUIRE(bagFileOpen(&handle, mode, bagFile.c_str()) == BAG_SUCCESS); + return handle; + } + + BagHandle* openBagRW(const std::string& bagFile) + { + BagHandle* handle = nullptr; + REQUIRE(bagFileOpen(&handle, BAG_OPEN_READ_WRITE, bagFile.c_str()) == BAG_SUCCESS); + return handle; + } + + BagHandle* createEmptyBagRW(const std::string& bagFile) + { + constexpr uint64_t kChunkSize = 100; + constexpr int kCompressionLevel = 6; + + // Create dataset + { + BAG::Metadata metadata; + metadata.loadFromBuffer(kMetadataXML); + auto pDataset = BAG::Dataset::create(bagFile, std::move(metadata), kChunkSize, + kCompressionLevel); + REQUIRE(pDataset); + } + BagHandle* handle = nullptr; + REQUIRE(bagFileOpen(&handle, BAG_OPEN_READ_WRITE, bagFile.c_str()) == BAG_SUCCESS); + return handle; + } +} + +// ============================================================================= +// FILE LIFECYCLE +// ============================================================================= + +TEST_CASE("FileOpen - Happy path", "[bag_c_api][lifecycle][file_open][happy]") { + BagHandle* handle = nullptr; + auto bfp = sampleBagPath(); + CHECK(bagFileOpen(&handle, BAG_OPEN_MODE::BAG_OPEN_READONLY, bfp.c_str()) == BAG_SUCCESS); + REQUIRE(handle != nullptr); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("FileOpen - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][lifecycle][file_open][errors][nullHandle]") { + REQUIRE(bagFileOpen(nullptr, BAG_OPEN_MODE::BAG_OPEN_READONLY, "test.bag") == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("FileOpen - NULL filename returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][lifecycle][file_open][errors][nullFilename]") { + BagHandle* handle = nullptr; + REQUIRE(bagFileOpen(&handle, BAG_OPEN_MODE::BAG_OPEN_READONLY, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + CHECK(handle == nullptr); +} + +TEST_CASE("FileOpen - Non-existent file returns BAG_NO_FILE_FOUND", "[bag_c_api][lifecycle][file_open][errors][noent]") { + BagHandle* handle = nullptr; + REQUIRE(bagFileOpen(&handle, BAG_OPEN_MODE::BAG_OPEN_READONLY, "non_existent_bag_file_12345.bag") == BAG_NO_FILE_FOUND); + CHECK(handle == nullptr); +} + +TEST_CASE("FileClose - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][lifecycle][file_close][errors][nullHandle]") { + REQUIRE(bagFileClose(nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("FileClose - Happy path returns BAG_SUCCESS", "[bag_c_api][lifecycle][file_close][happy]") { + BagHandle* handle = openSampleBag(); + CHECK(handle != nullptr); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// CREATION +// ============================================================================= + +TEST_CASE("CreateFromFile - Happy Path", "[bag_c_api][creation][file][happy]") +{ + const TestUtils::RandomFileGuard tmpFileName; + BagHandle* handle = nullptr; + BagError err = bagCreateFromFile(&handle, tmpFileName.m_fileName.c_str(), + sampleMetadataPath().c_str()); + REQUIRE(err == BAG_SUCCESS); + // Cover bagCreateLayer + err = bagCreateLayer(handle, Nominal_Elevation); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateFromFile - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][creation][file][errors][nullHandle]") { + REQUIRE(bagCreateFromFile(nullptr, "test.bag", "metadata.xml") == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("CreateFromFile - NULL fileName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][creation][file][errors][nullFilename]") { + BagHandle* handle = nullptr; + REQUIRE(bagCreateFromFile(&handle, nullptr, "metadata.xml") == BAG_INVALID_FUNCTION_ARGUMENT); + CHECK(handle == nullptr); +} + +TEST_CASE("CreateFromFile - NULL metadataFile returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][creation][file][errors][nullMetadata]") { + BagHandle* handle = nullptr; + REQUIRE(bagCreateFromFile(&handle, "test.bag", nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + CHECK(handle == nullptr); +} + +TEST_CASE("CreateFromBuffer - NULL filename returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][creation][buffer][errors][nullFilename]") { + BagHandle* handle = nullptr; + uint8_t dummy[] = {0xDE, 0xAD, 0xBE, 0xEF}; + REQUIRE(bagCreateFromBuffer(&handle, nullptr, dummy, sizeof(dummy)) == BAG_INVALID_FUNCTION_ARGUMENT); + CHECK(handle == nullptr); +} + +TEST_CASE("CreateFromBuffer - NULL buffer returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][creation][buffer][errors][nullMetadata]") { + BagHandle* handle = nullptr; + REQUIRE(bagCreateFromBuffer(&handle, "test.bag", nullptr, 10) == BAG_INVALID_FUNCTION_ARGUMENT); + CHECK(handle == nullptr); +} + +// ============================================================================= +// CREATE LAYER +// ============================================================================= + +TEST_CASE("CreateLayer - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][creation][layer][errors][nullHandle]") { + REQUIRE(bagCreateLayer(nullptr, Elevation) == BAG_INVALID_BAG_HANDLE); +} + +// ============================================================================= +// GRID & SPATIAL INFO +// ============================================================================= + +TEST_CASE("GetGridDimensions - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][grid][errors][nullHandle]") { + uint32_t rows = 0, cols = 0; + REQUIRE(bagGetGridDimensions(nullptr, &rows, &cols) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGridDimensions - NULL rows pointer returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullRows]") { + BagHandle* handle = openSampleBag(); + uint32_t cols = 0; + CHECK(bagGetGridDimensions(handle, nullptr, &cols) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGridDimensions - NULL cols pointer returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullCols]") { + BagHandle* handle = openSampleBag(); + uint32_t rows = 0; + CHECK(bagGetGridDimensions(handle, &rows, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGridDimensions - Happy path", "[bag_c_api][grid][happy]") { + BagHandle* handle = openSampleBag(); + uint32_t rows = 0, cols = 0; + CHECK(bagGetGridDimensions(handle, &rows, &cols) == BAG_SUCCESS); + CHECK(rows > 0); + CHECK(cols > 0); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetSpacing - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][grid][errors][nullHandle]") { + double rs = 0, cs = 0; + REQUIRE(bagGetSpacing(nullptr, &rs, &cs) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetSpacing - NULL rowSpacing returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullRowSpacing]") { + BagHandle* handle = openSampleBag(); + double cs = 0; + CHECK(bagGetSpacing(handle, nullptr, &cs) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetSpacing - NULL columnSpacing returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullColumnSpacing]") { + BagHandle* handle = openSampleBag(); + double rs = 0; + CHECK(bagGetSpacing(handle, &rs, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetSpacing - Happy path", "[bag_c_api][grid][happy]") { + BagHandle* handle = openSampleBag(); + double rs = 0.0, cs = 0.0; + CHECK(bagGetSpacing(handle, &rs, &cs) == BAG_SUCCESS); + CHECK(rs > 0.0); + CHECK(cs > 0.0); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeoCover - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][grid][errors][nullHandle]") { + double llx, lly, urx, ury; + REQUIRE(bagGetGeoCover(nullptr, &llx, &lly, &urx, &ury) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeoCover - NULL llx returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullLlx]") { + BagHandle* handle = openSampleBag(); + double lly, urx, ury; + CHECK(bagGetGeoCover(handle, nullptr, &lly, &urx, &ury) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeoCover - NULL lly returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullLly]") { + BagHandle* handle = openSampleBag(); + double llx, urx, ury; + CHECK(bagGetGeoCover(handle, &llx, nullptr, &urx, &ury) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeoCover - NULL urx returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullUrx]") { + BagHandle* handle = openSampleBag(); + double llx, lly, ury; + CHECK(bagGetGeoCover(handle, &llx, &lly, nullptr, &ury) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeoCover - NULL ury returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][grid][errors][nullUry]") { + BagHandle* handle = openSampleBag(); + double llx, lly, urx; + CHECK(bagGetGeoCover(handle, &llx, &lly, &urx, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeoCover - Happy path", "[bag_c_api][grid][happy]") { + BagHandle* handle = openSampleBag(); + double llx, lly, urx, ury; + CHECK(bagGetGeoCover(handle, &llx, &lly, &urx, &ury) == BAG_SUCCESS); + CHECK(urx > llx); + CHECK(ury > lly); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// SIMPLE LAYER MIN/MAX +// ============================================================================= + +TEST_CASE("GetMinMaxSimple - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][minmax][read][errors][nullHandle]") { + float mn, mx; + REQUIRE(bagGetMinMaxSimple(nullptr, Elevation, &mn, &mx) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetMinMaxSimple - NULL minValue returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][minmax][read][errors][nullMin]") { + BagHandle* handle = openSampleBag(); + float mx = 0.0f; + CHECK(bagGetMinMaxSimple(handle, Elevation, nullptr, &mx) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetMinMaxSimple - NULL maxValue returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][minmax][read][errors][nullMax]") { + BagHandle* handle = openSampleBag(); + float mn = 0.0f; + CHECK(bagGetMinMaxSimple(handle, Elevation, &mn, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetMinMaxSimple - Layer not found returns BAG_LAYER_MISSING", "[bag_c_api][minmax][read][errors][layerNotFound]") { + BagHandle* handle = openSampleBag(); + float mn, mx; + CHECK(bagGetMinMaxSimple(handle, static_cast(999), &mn, &mx) == BAG_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetMinMaxSimple - Happy path", "[bag_c_api][minmax][read][happy]") { + BagHandle* handle = openSampleBag(); + float mn = 0.0f, mx = 0.0f; + CHECK(bagGetMinMaxSimple(handle, Elevation, &mn, &mx) == BAG_SUCCESS); + CHECK(mx >= mn); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// SET MIN/MAX SIMPLE +// ============================================================================= + +TEST_CASE("SetMinMaxSimple - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][minmax][write][errors][nullHandle]") { + REQUIRE(bagSetMinMaxSimple(nullptr, Elevation, 0.0f, 1.0f) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("SetMinMaxSimple - NULL minValue (0.0) returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][minmax][write][errors][nullMin]") { + BagHandle* handle = openSampleBag(); + CHECK(bagSetMinMaxSimple(handle, Elevation, 0.0f, 1.0f) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetMinMaxSimple - NULL maxValue (0.0) returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][minmax][write][errors][nullMax]") { + BagHandle* handle = openSampleBag(); + CHECK(bagSetMinMaxSimple(handle, Elevation, 1.0f, 0.0f) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetMinMaxSimple - Layer missing returns BAG_SIMPLE_LAYER_MISSING", "[bag_c_api][minmax][write][errors][layerNotFound]") { + BagHandle* handle = openSampleBag(); + CHECK(bagSetMinMaxSimple(handle, static_cast(999), 1.0f, 2.0f) == BAG_SIMPLE_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetMinMaxSimple - Happy path (read-write)", "[bag_c_api][minmax][write][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagSetMinMaxSimple(handle, Elevation, 1.0f, 100.0f) == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// NUM LAYERS +// ============================================================================= + +TEST_CASE("GetNumLayers - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][layers][errors]") { + uint32_t num = 0; + REQUIRE(bagGetNumLayers(nullptr, &num) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetNumLayers - NULL numLayers returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][layers][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetNumLayers(handle, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetNumLayers - Happy path", "[bag_c_api][layers][happy]") { + BagHandle* handle = openSampleBag(); + uint32_t num = 0; + CHECK(bagGetNumLayers(handle, &num) == BAG_SUCCESS); + CHECK(num > 0); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// CONTAINS LAYER +// ============================================================================= + +TEST_CASE("ContainsLayer - NULL handle returns false + BAG_INVALID_BAG_HANDLE", "[bag_c_api][layers][errors]") { + BagError err; + bool result = bagContainsLayer(nullptr, Elevation, "elevation", &err); + CHECK_FALSE(result); + REQUIRE(err == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ContainsLayer - Georef_Metadata with NULL name returns false + error", "[bag_c_api][layers][errors]") { + BagHandle* handle = openSampleBag(); + BagError err; + bool result = bagContainsLayer(handle, Georef_Metadata, nullptr, &err); + CHECK_FALSE(result); + CHECK(err == BAG_GEOREF_METADATA_LAYER_NAME_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ContainsLayer - Georef_Metadata with empty name returns false + error", "[bag_c_api][layers][errors]") { + BagHandle* handle = openSampleBag(); + BagError err; + bool result = bagContainsLayer(handle, Georef_Metadata, "", &err); + CHECK_FALSE(result); + CHECK(err == BAG_GEOREF_METADATA_LAYER_NAME_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ContainsLayer - Happy path (existing layer)", "[bag_c_api][layers][happy]") { + BagHandle* handle = openSampleBag(); + BagError err; + bool result = bagContainsLayer(handle, Elevation, "elevation", &err); + CHECK(err == BAG_SUCCESS); + CHECK(result == true); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ContainsLayer - Non-existent layer returns false", "[bag_c_api][layers][happy]") { + BagHandle* handle = openSampleBag(); + BagError err; + bool result = bagContainsLayer(handle, Elevation, "nonexistent_layer_xyz", &err); + CHECK(err == BAG_SUCCESS); + CHECK_FALSE(result); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// READ / WRITE +// ============================================================================= + +TEST_CASE("Read - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][read_write][errors]") { + uint8_t* data = nullptr; + double x, y; + REQUIRE(bagRead(nullptr, 0, 0, 1, 1, Elevation, "elevation", &data, &x, &y) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("Read - NULL data returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][read_write][errors]") { + BagHandle* handle = openSampleBag(); + double x, y; + CHECK(bagRead(handle, 0, 0, 1, 1, Elevation, "elevation", nullptr, &x, &y) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Read - NULL x returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][read_write][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t* data = nullptr; + double y; + CHECK(bagRead(handle, 0, 0, 1, 1, Elevation, "elevation", &data, nullptr, &y) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Read - NULL y returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][read_write][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t* data = nullptr; + double x; + CHECK(bagRead(handle, 0, 0, 1, 1, Elevation, "elevation", &data, &x, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Read - Georef_Metadata without name returns BAG_GEOREF_METADATA_LAYER_NAME_MISSING", "[bag_c_api][read_write][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t* data = nullptr; + double x, y; + CHECK(bagRead(handle, 0, 0, 1, 1, Georef_Metadata, nullptr, &data, &x, &y) == BAG_GEOREF_METADATA_LAYER_NAME_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Read - Non-existent layer returns BAG_HDF_DATASET_OPEN_FAILURE", "[bag_c_api][read_write][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t* data = nullptr; + double x, y; + CHECK(bagRead(handle, 0, 0, 1, 1, Elevation, "nonexistent", &data, &x, &y) == BAG_HDF_DATASET_OPEN_FAILURE); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Read - Happy path", "[bag_c_api][read_write][happy]") { + BagHandle* handle = openSampleBag(); + uint32_t rows, cols; + CHECK(bagGetGridDimensions(handle, &rows, &cols) == BAG_SUCCESS); + + uint8_t* data = nullptr; + double x, y; + BagError err = bagRead(handle, 0, 0, 1, 1, Elevation, "elevation", &data, &x, &y); + if (err == BAG_SUCCESS) + { + CHECK(data != nullptr); + CHECK(x != 0.0); + CHECK(y != 0.0); + bagFree(data); + } + else + { + // sample.bag may not have elevation layer with that name + CHECK(err == BAG_HDF_DATASET_OPEN_FAILURE); + } + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Write - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][read_write][errors]") { + BagHandle* h = nullptr; + uint8_t *d = nullptr; + const auto l = "elevation"; + auto result = bagWrite(h, 0u, 0u, 1u, 1u, Elevation, l, d); + REQUIRE(result == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("Write - NULL data returns BAG_SUCCESS (nothing to write)", "[bag_c_api][read_write][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagWrite(handle, 0, 0, 1, 1, Elevation, "elevation", nullptr) == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Write - Georef_Metadata without name returns BAG_GEOREF_METADATA_LAYER_NAME_MISSING", "[bag_c_api][read_write][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + uint8_t dummy = 0; + CHECK(bagWrite(handle, 0, 0, 1, 1, Georef_Metadata, nullptr, &dummy) == BAG_GEOREF_METADATA_LAYER_NAME_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("Write - Non-existent layer returns BAG_HDF_DATASET_OPEN_FAILURE", "[bag_c_api][read_write][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + uint8_t dummy = 0; + CHECK(bagWrite(handle, 0, 0, 1, 1, Elevation, "nonexistent", &dummy) == BAG_HDF_DATASET_OPEN_FAILURE); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// GET ERROR STRING - base errors +// ============================================================================= + +TEST_CASE("GetErrorString - NULL error pointer returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][errors][string]") { + CHECK(bagGetErrorString(BAG_SUCCESS, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); +} + +TEST_CASE("GetErrorString - BAG_SUCCESS", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_SUCCESS, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Bag returned a successful completion"); +} + +TEST_CASE("GetErrorString - BAG_BAD_FILE_IO_OPERATION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_BAD_FILE_IO_OPERATION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Bad status with file IO operation"); +} + +TEST_CASE("GetErrorString - BAG_NO_FILE_FOUND", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_NO_FILE_FOUND, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "File not found"); +} + +TEST_CASE("GetErrorString - BAG_NO_ACCESS_PERMISSION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_NO_ACCESS_PERMISSION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Failed to access Bag because of permissions"); +} + +TEST_CASE("GetErrorString - BAG_MEMORY_ALLOCATION_FAILED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_MEMORY_ALLOCATION_FAILED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Memory allocation failed"); +} + +TEST_CASE("GetErrorString - BAG_INVALID_BAG_HANDLE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_INVALID_BAG_HANDLE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Invalid or NULL bagHandle"); +} + +TEST_CASE("GetErrorString - BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_INVALID_FUNCTION_ARGUMENT, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Invalid function argument or illegal value passed to Bag"); +} + +TEST_CASE("GetErrorString - BAG_LAYER_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_LAYER_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The specified layer is missing"); +} + +TEST_CASE("GetErrorString - BAG_SIMPLE_LAYER_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_SIMPLE_LAYER_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The specified simple layer is missing"); +} + +TEST_CASE("GetErrorString - BAG_WRONG_DESCRIPTOR_FOUND", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_WRONG_DESCRIPTOR_FOUND, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The wrong type of descriptor found for this layer"); +} + +TEST_CASE("GetErrorString - BAG_INVALID_LAYER_TYPE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_INVALID_LAYER_TYPE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Layer type larger than UNKNOWN_LAYER_TYPE encountered"); +} + +TEST_CASE("GetErrorString - BAG_UNSPECIFIED_ERROR", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_UNSPECIFIED_ERROR, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "An unspecified error has been encountered"); +} + +TEST_CASE("GetErrorString - BAG_SURFACE_CORRECTIONS_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_SURFACE_CORRECTIONS_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The surface corrections layer is missing"); +} + +TEST_CASE("GetErrorString - BAG_MORE_BAG_INSTANCES_PRESENT", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_MORE_BAG_INSTANCES_PRESENT, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "There are still instances of the BAG in memory"); +} + +// ============================================================================= +// GET ERROR STRING - Metadata errors +// ============================================================================= + +TEST_CASE("GetErrorString - BAG_METADTA_NO_HOME", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_NO_HOME, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The BAG_HOME environment variable must be set to the configdata directory of the openns distribution"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_SCHEMA_FILE_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_SCHEMA_FILE_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata schema file(s) (*.xsd) was missing, they should be within configdata under smXML or ISO19139/bag. Check BAG_HOME path"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_PARSE_MEM_EXCEPTION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_PARSE_MEM_EXCEPTION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata exceeded memory limit during parsing"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_PARSE_EXCEPTION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_PARSE_EXCEPTION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata parsing encountered an fatal exception"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_PARSE_DOM_EXCEPTION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_PARSE_DOM_EXCEPTION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata parsing encountered a DOM (document object model) exception, impossible to complete operation"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_PARSE_UNK_EXCEPTION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_PARSE_UNK_EXCEPTION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata parsing encountered an unknown error"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_PARSE_FAILED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_PARSE_FAILED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata parsing failed, unable to parse input file"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_PARSE_FAILED_MEM", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_PARSE_FAILED_MEM, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata parsing failed, unable to parse specified input buffer memory overflow"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_VALIDATE_FAILED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_VALIDATE_FAILED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata XML validation failed"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INVALID_HANDLE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INVALID_HANDLE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata Invalid (NULL) bagHandle supplied to an accessor method"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INIT_FAILED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INIT_FAILED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata Initialization of the low level XML support system failed"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_NO_PROJECTION_INFO", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_NO_PROJECTION_INFO, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata No projection information was found in the XML supplied"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INSUFFICIENT_BUFFER", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INSUFFICIENT_BUFFER, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata supplied buffer is not large enough to hold the extracted contents from XML"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INCOMPLETE_COVER", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INCOMPLETE_COVER, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata One or more elements of the requested coverage are missing from the XML file"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INVLID_DIMENSIONS", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INVLID_DIMENSIONS, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata The number of dimensions is incorrect (not equal to 2)"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_UNCRT_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_UNCRT_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata 'uncertaintyType' information is missing from the XML structure"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_BUFFER_EXCEEDED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_BUFFER_EXCEEDED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata supplied buffer is too large to be stored in the internal array"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_DPTHCORR_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_DPTHCORR_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The 'depthCorrectionType' information is missing from the XML structure"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_RESOLUTION_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_RESOLUTION_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata resolution information is missing from the XML structure"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INVALID_PROJECTION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INVALID_PROJECTION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata unsupported projection being used"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INVALID_DATUM", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INVALID_DATUM, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata unsupported datum being used"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INVALID_HREF", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INVALID_HREF, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata horizontal reference system is invalid"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_INVALID_VREF", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_INVALID_VREF, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Metadata vertical reference system is invalid"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_SCHEMA_SETUP_FAILED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_SCHEMA_SETUP_FAILED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Failed to setup the xml schema"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_SCHEMA_VALIDATION_SETUP_FAILED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_SCHEMA_VALIDATION_SETUP_FAILED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Failed to setup the xml schema validation"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_EMPTY_DOCUMENT", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_EMPTY_DOCUMENT, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The metadata document is emtpy"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_MISSING_MANDATORY_ITEM", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_MISSING_MANDATORY_ITEM, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The metadata is missing a mandatory item"); +} + +TEST_CASE("GetErrorString - BAG_METADTA_NOT_INITIALIZED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_METADTA_NOT_INITIALIZED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "The metadata has not been initialized correctly"); +} + +// ============================================================================= +// GET ERROR STRING - HDF errors +// ============================================================================= + +TEST_CASE("GetErrorString - BAG_NOT_HDF5_FILE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_NOT_HDF5_FILE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Bag is not an HDF5 File"); +} + +TEST_CASE("GetErrorString - BAG_HDF_RANK_INCOMPATIBLE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_RANK_INCOMPATIBLE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Bag's rank is incompatible with expected Rank of the Datasets: 2"); +} + +TEST_CASE("GetErrorString - BAG_HDF_TYPE_NOT_FOUND", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_TYPE_NOT_FOUND, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Bag surface Datatype parameter not available"); +} + +TEST_CASE("GetErrorString - BAG_HDF_DATASPACE_CORRUPTED", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_DATASPACE_CORRUPTED, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Dataspace for a bag surface is corrupted or could not be read"); +} + +TEST_CASE("GetErrorString - BAG_HDF_ACCESS_EXTENTS_ERROR", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_ACCESS_EXTENTS_ERROR, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure in request for access outside the extents of a bag surface's Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CANNOT_WRITE_NULL_DATA", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CANNOT_WRITE_NULL_DATA, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Cannot write NULL or uninitialized data to Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_INTERNAL_ERROR", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_INTERNAL_ERROR, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF There was an internal HDF error detected"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CREATE_FILE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CREATE_FILE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create new HDF Bag File"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CREATE_DATASPACE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CREATE_DATASPACE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create the Dataspace"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CREATE_PROPERTY_CLASS_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CREATE_PROPERTY_CLASS_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create the Property class"); +} + +TEST_CASE("GetErrorString - BAG_HDF_SET_PROPERTY_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_SET_PROPERTY_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to set value of Property class"); +} + +TEST_CASE("GetErrorString - BAG_HDF_TYPE_COPY_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_TYPE_COPY_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failed to copy Datatype parameter for Dataset access"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CREATE_DATASET_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CREATE_DATASET_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create the Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_DATASET_EXTEND_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_DATASET_EXTEND_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Cannot extend Dataset extents"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CREATE_ATTRIBUTE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CREATE_ATTRIBUTE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create Attribute"); +} + +TEST_CASE("GetErrorString - BAG_HDF_CREATE_GROUP_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_CREATE_GROUP_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create Group"); +} + +TEST_CASE("GetErrorString - BAG_HDF_WRITE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_WRITE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure writing to Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_READ_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_READ_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure reading from Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_GROUP_CLOSE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_GROUP_CLOSE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure closing Group"); +} + +TEST_CASE("GetErrorString - BAG_HDF_FILE_CLOSE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_FILE_CLOSE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure closing File"); +} + +TEST_CASE("GetErrorString - BAG_HDF_FILE_OPEN_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_FILE_OPEN_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to open File"); +} + +TEST_CASE("GetErrorString - BAG_HDF_GROUP_OPEN_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_GROUP_OPEN_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to open Group"); +} + +TEST_CASE("GetErrorString - BAG_HDF_ATTRIBUTE_OPEN_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_ATTRIBUTE_OPEN_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to open Attribute"); +} + +TEST_CASE("GetErrorString - BAG_HDF_ATTRIBUTE_CLOSE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_ATTRIBUTE_CLOSE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure closing Attribute"); +} + +TEST_CASE("GetErrorString - BAG_HDF_DATASET_CLOSE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_DATASET_CLOSE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Failure closing Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_DATASET_OPEN_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_DATASET_OPEN_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to open Dataset"); +} + +TEST_CASE("GetErrorString - BAG_HDF_TYPE_CREATE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_TYPE_CREATE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to create Datatype"); +} + +TEST_CASE("GetErrorString - BAG_HDF_INVALID_COMPRESSION_LEVEL", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_INVALID_COMPRESSION_LEVEL, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF compression level not in acceptable range of 0 to 9"); +} + +TEST_CASE("GetErrorString - BAG_HDF_WRITE_ATTRIBUTE_FAILURE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_HDF_WRITE_ATTRIBUTE_FAILURE, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "HDF Unable to write to Attribute"); +} + +// ============================================================================= +// GET ERROR STRING - Georef metadata errors +// ============================================================================= + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: The specified layer does not exist"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_RECORD_NOT_FOUND", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_RECORD_NOT_FOUND, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: Unable to find record"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_FIELD_NOT_FOUND", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_FIELD_NOT_FOUND, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: Unable to find field"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_NO_VALUE_FOUND", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_NO_VALUE_FOUND, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: Unable to find the value"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_INVALID_RECORD_DEFINITION", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_INVALID_RECORD_DEFINITION, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: The provided record does not match the definition in the georeferenced metadata layer"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_NAME_MISSING", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_NAME_MISSING, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: A georeferenced metadata layer name is required"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_EXISTS", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_EXISTS, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: The specified layer already exists"); +} + +TEST_CASE("GetErrorString - BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN, &err) == BAG_SUCCESS); + CHECK(std::string(reinterpret_cast(err)) == "Georef: The specified metadata profile is unknown"); +} + +TEST_CASE("GetErrorString - BAG_INVALID_ERROR_CODE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(BAG_INVALID_ERROR_CODE, &err) == BAG_INVALID_ERROR_CODE); + CHECK(std::string(reinterpret_cast(err)) == "An undefined bagError code was encountered"); +} + +// ============================================================================= +// GET ERROR STRING - Invalid error code +// ============================================================================= + +TEST_CASE("GetErrorString - Invalid code (default) returns BAG_INVALID_ERROR_CODE", "[bag_c_api][errors][string]") { + uint8_t* err = nullptr; + CHECK(bagGetErrorString(static_cast(99999), &err) == BAG_INVALID_ERROR_CODE); + CHECK(std::string(reinterpret_cast(err)) == "An undefined bagError code was encountered"); +} + +// ============================================================================= +// COORDINATES +// ============================================================================= + +TEST_CASE("ComputePosition - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][coordinates][errors]") { + double x, y; + REQUIRE(bagComputePostion(nullptr, 0, 0, &x, &y) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ComputePosition - NULL x returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][coordinates][errors]") { + BagHandle* handle = openSampleBag(); + double y; + CHECK(bagComputePostion(handle, 0, 0, nullptr, &y) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ComputePosition - NULL y returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][coordinates][errors]") { + BagHandle* handle = openSampleBag(); + double x; + CHECK(bagComputePostion(handle, 0, 0, &x, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ComputePosition - Happy path", "[bag_c_api][coordinates][pos][happy]") { + BagHandle* handle = openSampleBag(); + double x, y; + CHECK(bagComputePostion(handle, 50, 50, &x, &y) == BAG_SUCCESS); + CHECK(x == 688410.0); + CHECK(y == 5555120.0); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ComputeIndex - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][coordinates][errors]") { + uint32_t row, col; + REQUIRE(bagComputeIndex(nullptr, 1.0, 1.0, &row, &col) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ComputeIndex - NULL row returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][coordinates][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t col; + CHECK(bagComputeIndex(handle, 1.0, 1.0, nullptr, &col) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ComputeIndex - NULL col returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][coordinates][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t row; + CHECK(bagComputeIndex(handle, 1.0, 1.0, &row, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ComputeIndex - Happy path", "[bag_c_api][coordinates][idx][happy]") { + BagHandle* handle = openSampleBag(); + uint32_t row = 999, col = 999; + CHECK(bagComputeIndex(handle, 688410.0, 5555120.0, &row, &col) == BAG_SUCCESS); + CHECK(row == 50); + CHECK(col == 50); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// ALLOCATE BUFFER +// ============================================================================= + +TEST_CASE("AllocateBuffer - NULL handle returns nullptr", "[bag_c_api][memory][errors]") { + BagError err; + uint8_t* buf = bagAllocateBuffer(nullptr, 10, 10, Elevation, "elevation", &err); + CHECK(buf == nullptr); +} + +TEST_CASE("AllocateBuffer - NULL bagError returns nullptr", "[bag_c_api][memory][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t* buf = bagAllocateBuffer(handle, 10, 10, Elevation, "elevation", nullptr); + CHECK(buf == nullptr); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AllocateBuffer - Georef_Metadata with NULL name returns nullptr + error", "[bag_c_api][memory][errors]") { + BagHandle* handle = openSampleBag(); + BagError err; + uint8_t* buf = bagAllocateBuffer(handle, 10, 10, Georef_Metadata, nullptr, &err); + CHECK(buf == nullptr); + CHECK(err == BAG_GEOREF_METADATA_LAYER_NAME_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AllocateBuffer - Georef_Metadata with empty name returns nullptr + error", "[bag_c_api][memory][errors]") { + BagHandle* handle = openSampleBag(); + BagError err; + uint8_t* buf = bagAllocateBuffer(handle, 10, 10, Georef_Metadata, "", &err); + CHECK(buf == nullptr); + CHECK(err == BAG_GEOREF_METADATA_LAYER_NAME_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AllocateBuffer - Non-existent layer returns nullptr + BAG_LAYER_MISSING", "[bag_c_api][memory][errors]") { + BagHandle* handle = openSampleBag(); + BagError err; + uint8_t* buf = bagAllocateBuffer(handle, 10, 10, Elevation, "nonexistent_layer", &err); + CHECK(buf == nullptr); + CHECK(err == BAG_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AllocateBuffer - Happy path (existing layer)", "[bag_c_api][memory][happy]") { + BagHandle* handle = openSampleBag(); + BagError err; + uint8_t* buf = bagAllocateBuffer(handle, 5, 5, Elevation, "elevation", &err); + if (err == BAG_SUCCESS) + { + CHECK(buf != nullptr); + bagFree(buf); + } + else + { + // Layer may not exist in sample + CHECK(err == BAG_LAYER_MISSING); + } + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// ALLOCATE / FREE +// ============================================================================= + +TEST_CASE("Allocate - returns valid buffer of correct size", "[bag_c_api][memory][happy]") { + const uint32_t size = 256; + uint8_t* buf = bagAllocate(size); + REQUIRE(buf != nullptr); + std::memset(buf, 0x42, size); + for (uint32_t i = 0; i < size; ++i) + CHECK(buf[i] == 0x42); + bagFree(buf); +} + +TEST_CASE("Free - NULL pointer is safe (no-op / no crash)", "[bag_c_api][memory][happy]") { + bagFree(nullptr); // Should not crash +} + +// ============================================================================= +// SURFACE CORRECTIONS - READ/WRITE VERTICAL DATUM +// ============================================================================= + +TEST_CASE("ReadCorrectorVerticalDatum - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + uint8_t datum[256] = {0}; + REQUIRE(bagReadCorrectorVerticalDatum(nullptr, 1, datum) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadCorrectorVerticalDatum - corrector=0 returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t datum[256] = {0}; + CHECK(bagReadCorrectorVerticalDatum(handle, 0, datum) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectorVerticalDatum - corrector=11 returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + uint8_t datum[256] = {0}; + CHECK(bagReadCorrectorVerticalDatum(handle, 11, datum) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectorVerticalDatum - NULL datum returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagReadCorrectorVerticalDatum(handle, 1, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteCorrectorVerticalDatum - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][vdatum][errors][nullHandle]") { + uint8_t datum[] = "MLLW"; + REQUIRE(bagWriteCorrectorVerticalDatum(nullptr, 1, datum) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("WriteCorrectorVerticalDatum - corrector=0 returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][vdatum][errors][correctorZero]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + uint8_t datum[] = "MLLW"; + CHECK(bagWriteCorrectorVerticalDatum(handle, 0, datum) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteCorrectorVerticalDatum - corrector=11 returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + uint8_t datum[] = "MLLW"; + CHECK(bagWriteCorrectorVerticalDatum(handle, 11, datum) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteCorrectorVerticalDatum - NULL inDatum returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagWriteCorrectorVerticalDatum(handle, 1, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteCorrectorVerticalDatum - No surface corrections returns BAG_SURFACE_CORRECTIONS_MISSING", "[bag_c_api][surface_corrections][errors][noCorrections]") { + const TestUtils::RandomFileGuard tmpFileName; + BagHandle* handle = createEmptyBagRW(tmpFileName); + uint8_t datum[] = "MLLW"; + BagError err = bagWriteCorrectorVerticalDatum(handle, 1, datum); + CHECK(err == BAG_SURFACE_CORRECTIONS_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// SURFACE CORRECTIONS - CREATE/READ CORRECTED LAYER/REGION/ROW/NODE +// ============================================================================= + +TEST_CASE("CreateReadCorrectedLayer - Happy Path", "[bag_c_api][surface_corrections][readWrite][happy]") +{ + // Forward declare stack variables for later use (we have to do this above calls to goto cleanup). + float *surfRead = nullptr; + double *xRead = nullptr; + double *yRead = nullptr; + const BAG_SURFACE_CORRECTION_TOPOGRAPHY kExpectedSurfaceType = BAG_SURFACE_GRID_EXTENTS; + const uint8_t kExpectedNumCorrectors = 3; + BAG_SURFACE_CORRECTION_TOPOGRAPHY topo{}; + uint8_t numCorr = 0; + const float kExpectedItem0[] = {9.87f, 6.543f, 2.109876f}; + float *corrected = nullptr; + std::string datum1 = "MLLW"; + std::string datum2 = "Ellipsoid"; + std::string datum3 = "NAVD88"; + uint8_t datumRead[256]; + + bool success = true; + const TestUtils::RandomFileGuard tmpFile; + auto tmpFileName = tmpFile.m_fileName; + BagHandle* handle = nullptr; + auto mdBuff = const_cast(reinterpret_cast(kMetadataXML.data())); + // Create new BAG + BagError err = bagCreateFromBuffer(&handle, tmpFileName.c_str(), mdBuff, (uint32_t)kMetadataXML.size()); + REQUIRE(err == BAG_SUCCESS); + // Write some elevation data so that we can read corrected data below + const uint32_t kGridSize = 100; + auto *surf = static_cast(malloc(sizeof(float) * kGridSize * kGridSize)); + // Write the data. + constexpr uint32_t columnStart = 0; + constexpr uint32_t columnEnd = kGridSize - 1; + for(uint32_t row=0; row(kGridSize)); + } + } + err = bagWrite(handle, 0, columnStart, 0, columnEnd, + Elevation, "elevation", reinterpret_cast(surf)); + if (err != BAG_SUCCESS) { + success = false; + goto cleanup; + } + + // Read back the data + surfRead = (float *)malloc(sizeof(float) * kGridSize * kGridSize); + xRead = (double *)malloc(sizeof(double) * kGridSize); + yRead = (double *)malloc(sizeof(double) * kGridSize); + err = bagRead(handle, 0, 0, columnEnd, columnEnd, + Elevation, "elevation", reinterpret_cast((&surfRead)), xRead, yRead); + if (err != BAG_SUCCESS) { + success = false; + goto cleanup; + } + for(uint32_t row=0; row(datum1.data())); + CHECK(err == BAG_SUCCESS); + err = bagWriteCorrectorVerticalDatum(handle, 2, reinterpret_cast(datum2.data())); + CHECK(err == BAG_SUCCESS); + err = bagWriteCorrectorVerticalDatum(handle, 3, reinterpret_cast(datum3.data())); + CHECK(err == BAG_SUCCESS); + // Read vertical datums back + err = bagReadCorrectorVerticalDatum(handle, 1, datumRead); + CHECK(err == BAG_SUCCESS); + CHECK(datum1 == std::string{reinterpret_cast(datumRead)}); + memset(datumRead, '\0', 256); + err = bagReadCorrectorVerticalDatum(handle, 2, datumRead); + CHECK(err == BAG_SUCCESS); + CHECK(datum2 == std::string{reinterpret_cast(datumRead)}); + memset(datumRead, '\0', 256); + err = bagReadCorrectorVerticalDatum(handle, 2, datumRead); + CHECK(err == BAG_SUCCESS); + CHECK(datum2 == std::string{reinterpret_cast(datumRead)}); + memset(datumRead, '\0', 256); + + // Read correctors back + // Cover bagReadCorrectedRow() + corrected = (float *)malloc(sizeof(float) * kGridSize); + err = bagReadCorrectedRow(handle, 0, 1, Elevation, &corrected); + CHECK(err == BAG_SUCCESS); + // The first value in the dummy surface is ~0.01, so ~9.88 corrected. + CHECK_THAT(corrected[0], Catch::Matchers::WithinAbs(9.88f, 0.000001)); + free(corrected); + corrected = nullptr; + + // Cover bagReadCorrectedNode + corrected = (float *)malloc(sizeof(float)); + err = bagReadCorrectedNode(handle, 0, 0, 1, Elevation, &corrected); + CHECK(err == BAG_SUCCESS); + // The first value in the dummy surface is ~0.01, so ~9.88 corrected. + CHECK_THAT(*corrected, Catch::Matchers::WithinAbs(9.88f, 0.000001)); + free(corrected); + corrected = nullptr; + + // Read corrected surface back + corrected = (float *)malloc(sizeof(float)); + err = bagReadCorrectedRegion(handle, 0, 0, 0, 0, 1, Elevation, &corrected); + CHECK(err == BAG_SUCCESS); + // TODO: Make sure this value makes sense + CHECK_THAT(*corrected, Catch::Matchers::WithinAbs(1.86372696e-43, 0.000001)); + free(corrected); + corrected = nullptr; + // Read corrected surface back, this time using bagReadCorrectedLayer + corrected = (float *)malloc(sizeof(float)); + err = bagReadCorrectedLayer(handle, 1, Elevation, &corrected); + CHECK(err == BAG_SUCCESS); + // TODO: Make sure this value makes sense + CHECK_THAT(*corrected, Catch::Matchers::WithinAbs(1.86372696e-43, 0.000001)); + free(corrected); + corrected = nullptr; + + // Close bag, re-open read-only, and re-read corrector vertical datum + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); + handle = nullptr; + err = bagFileOpen(&handle, BAG_OPEN_READONLY, tmpFileName.c_str()); + REQUIRE(err == BAG_SUCCESS); + err = bagReadCorrectorVerticalDatum(handle, 1, datumRead); + CHECK(err == BAG_SUCCESS); + CHECK(datum1 == std::string{reinterpret_cast(datumRead)}); + memset(datumRead, '\0', 256); + err = bagReadCorrectorVerticalDatum(handle, 2, datumRead); + CHECK(err == BAG_SUCCESS); + CHECK(datum2 == std::string{reinterpret_cast(datumRead)}); + memset(datumRead, '\0', 256); + err = bagReadCorrectorVerticalDatum(handle, 2, datumRead); + CHECK(err == BAG_SUCCESS); + CHECK(datum2 == std::string{reinterpret_cast(datumRead)}); + memset(datumRead, '\0', 256); + +cleanup: + free(surf); + if (surfRead) free(surfRead); + if (xRead) free(xRead); + if (yRead) free(yRead); + if (corrected) free(corrected); + if (handle) REQUIRE(bagFileClose(handle) == BAG_SUCCESS); + REQUIRE(success); +} + +TEST_CASE("ReadCorrectedLayer - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + float* data = nullptr; + REQUIRE(bagReadCorrectedLayer(nullptr, 1, Elevation, &data) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadCorrectedLayer - NULL data returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagReadCorrectedLayer(handle, 1, Elevation, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedLayer - sample.bag has irregular corrections, but read corrected node only works on regular grid corrections", "[bag_c_api][surface_corrections][errors][noRegGridCorr]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedLayer(handle, 1, Elevation, &data); + CHECK(err == BAG_SURFACE_CORRECTIONS_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedLayer - No simple layer returns BAG_SIMPLE_LAYER_MISSING", "[bag_c_api][surface_corrections][errors][invalidLayerType]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedLayer(handle, 1, static_cast(999), &data); + CHECK(err == BAG_SIMPLE_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedRegion - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + float* data = nullptr; + REQUIRE(bagReadCorrectedRegion(nullptr, 0, 0, 1, 1, 1, Elevation, &data) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadCorrectedRegion - NULL data returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][region][errors][nullData]") { + BagHandle* handle = openSampleBag(); + CHECK(bagReadCorrectedRegion(handle, 0, 0, 1, 1, 1, Elevation, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedRegion - No surface corrections returns BAG_SURFACE_CORRECTIONS_MISSING", "[bag_c_api][surface_corrections][region][errors]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedRegion(handle, 0, 0, 1, 1, 1, Elevation, &data); + CHECK(err == BAG_SURFACE_CORRECTIONS_MISSING); + if (err == BAG_SUCCESS && data) bagFree(reinterpret_cast(data)); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedRegion - Invalid layer type", "[bag_c_api][surface_corrections][region][errors][invalidLayerType]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedRegion(handle, 0, 0, 1, 1, 1, static_cast(999), &data); + CHECK(err == BAG_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedRow - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + float* data = nullptr; + REQUIRE(bagReadCorrectedRow(nullptr, 0, 1, Elevation, &data) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadCorrectedRow - NULL data returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagReadCorrectedRow(handle, 0, 1, Elevation, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedRow - sample.bag has irregular corrections, but read corrected row only works on regular grid corrections", "[bag_c_api][surface_corrections][row][errors][noRegGridCorr]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedRow(handle, 0, 1, Elevation, &data); + CHECK(err == BAG_SURFACE_CORRECTIONS_MISSING); + if (err == BAG_SUCCESS && data) bagFree(reinterpret_cast(data)); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedRow - Invalid layer type", "[bag_c_api][surface_corrections][row][errors][invalidLayerType]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedRow(handle, 0, 1, static_cast(999), &data); + CHECK(err == BAG_INVALID_LAYER_TYPE); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedNode - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + float* data = nullptr; + REQUIRE(bagReadCorrectedNode(nullptr, 0, 0, 1, Elevation, &data) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadCorrectedNode - NULL data returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors][nullData]") { + BagHandle* handle = openSampleBag(); + CHECK(bagReadCorrectedNode(handle, 0, 0, 1, Elevation, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedNode - sample.bag has irregular corrections, but read corrected node only works on regular grid corrections", "[bag_c_api][surface_corrections][node][errors][noRegGridCorr]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedNode(handle, 0, 0, 1, Elevation, &data); + CHECK(err == BAG_SURFACE_CORRECTIONS_MISSING); + if (err == BAG_SUCCESS && data) bagFree(reinterpret_cast(data)); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectedNode - Invalid layer type", "[bag_c_api][surface_corrections][node][errors][invalidLayerType]") { + BagHandle* handle = openSampleBag(); + float* data = nullptr; + BagError err = bagReadCorrectedNode(handle, 0, 0, 1, static_cast(999), &data); + CHECK(err == BAG_INVALID_LAYER_TYPE); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// SURFACE CORRECTIONS - NUM CORRECTORS / TOPOGRAPHY / CREATE / DEFINITION +// ============================================================================= + +TEST_CASE("GetNumSurfaceCorrectors - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + uint8_t num = 0; + REQUIRE(bagGetNumSurfaceCorrectors(nullptr, &num) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetNumSurfaceCorrectors - NULL numCorrectors returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetNumSurfaceCorrectors(handle, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetNumSurfaceCorrectors - Happy path", "[bag_c_api][surface_corrections][happy]") { + BagHandle* handle = openSampleBag(); + uint8_t num = 0; + BagError err = bagGetNumSurfaceCorrectors(handle, &num); + // sample BAG should have 2 + CHECK(num == 2); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetSurfaceCorrectionTopography - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][topo][errors][nullHandle]") { + BAG_SURFACE_CORRECTION_TOPOGRAPHY topo = BAG_SURFACE_GRID_EXTENTS; + REQUIRE(bagGetSurfaceCorrectionTopography(nullptr, &topo) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetSurfaceCorrectionTopography - NULL type returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetSurfaceCorrectionTopography(handle, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetSurfaceCorrectionTopography - No surface corrections returns BAG_SURFACE_CORRECTIONS_MISSING", "[bag_c_api][surface_corrections][topo][errors][dbg]") { + BagHandle* handle = openSampleBag(); + BAG_SURFACE_CORRECTION_TOPOGRAPHY topo; + BagError err = bagGetSurfaceCorrectionTopography(handle, &topo); + CHECK(err == BAG_SUCCESS); + CHECK(topo == BAG_SURFACE_IRREGULARLY_SPACED); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateCorrectorLayer - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors][nullHandle]") { + REQUIRE(bagCreateCorrectorLayer(nullptr, 1, BAG_SURFACE_GRID_EXTENTS) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("WriteCorrectorDefinition - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + REQUIRE(bagWriteCorrectorDefinition(nullptr, nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("WriteCorrectorDefinition - NULL def returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagWriteCorrectorDefinition(handle, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteCorrectorDefinition - Happy Path", "[bag_c_api][surface_corrections][write][happy][emptyCorrections]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagVerticalCorrectorDef def{}; + BagError err = bagWriteCorrectorDefinition(handle, &def); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectorDefinition - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][surface_corrections][errors]") { + REQUIRE(bagReadCorrectorDefinition(nullptr, nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadCorrectorDefinition - NULL def returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][surface_corrections][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagReadCorrectorDefinition(handle, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadCorrectorDefinition - Happy", "[bag_c_api][surface_corrections][defn][happy]") { + BagHandle* handle = openSampleBag(); + BagVerticalCorrectorDef def{}; + BagError err = bagReadCorrectorDefinition(handle, &def); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// TRACKING LIST +// ============================================================================= + +TEST_CASE("TrackingListLength - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + uint32_t len = 0; + REQUIRE(bagTrackingListLength(nullptr, &len) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("TrackingListLength - NULL length returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagTrackingListLength(handle, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("TrackingListLength - Happy path", "[bag_c_api][tracking][happy]") { + BagHandle* handle = openSampleBag(); + uint32_t len = 0; + CHECK(bagTrackingListLength(handle, &len) == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListNode - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + BagTrackingItem* items = nullptr; + uint32_t num = 0; + REQUIRE(bagReadTrackingListNode(nullptr, 0, 0, &items, &num) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadTrackingListNode - NULL items returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t num = 0; + CHECK(bagReadTrackingListNode(handle, 0, 0, nullptr, &num) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListNode - NULL numItems returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + BagTrackingItem* items = nullptr; + CHECK(bagReadTrackingListNode(handle, 0, 0, &items, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteReadTrackingListNode - Happy path", "[bag_c_api][tracking][readWrite][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + BagHandle* handle = createEmptyBagRW(tmpFileName); + // Write some items + BagTrackingItem item1{1, 2, 3.4f, 5.6f, 7, 8}; + BagError err = bagWriteTrackingListItem(handle, &item1); + CHECK(err == BAG_SUCCESS); + BagTrackingItem item2{1, 2, 33.44f, 55.66f, 77, 88}; + err = bagWriteTrackingListItem(handle, &item2); + CHECK(err == BAG_SUCCESS); + // Read them back + BagTrackingItem* items = nullptr; + uint32_t num = 0; + CHECK(bagReadTrackingListNode(handle, 1, 2, &items, &num) == BAG_SUCCESS); + CHECK(items[0].depth == item1.depth); + CHECK(items[1].depth == item2.depth); + if (num > 0 && items) delete[] items; + // Cover bagReadTrackingListCode + items = nullptr; + num = 0; + CHECK(bagReadTrackingListCode(handle, 7, &items, &num) == BAG_SUCCESS); + CHECK(items[0].depth == item1.depth); + if (num > 0 && items) delete[] items; + // Cover bagReadTrackingListSeries + items = nullptr; + num = 0; + CHECK(bagReadTrackingListSeries(handle, 88, &items, &num) == BAG_SUCCESS); + CHECK(items[0].depth == item2.depth); + if (num > 0 && items) delete[] items; + + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListCode - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + BagTrackingItem* items = nullptr; + uint32_t num = 0; + REQUIRE(bagReadTrackingListCode(nullptr, 1, &items, &num) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadTrackingListCode - NULL items returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t num = 0; + CHECK(bagReadTrackingListCode(handle, 1, nullptr, &num) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListCode - NULL numItems returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + BagTrackingItem* items = nullptr; + CHECK(bagReadTrackingListCode(handle, 1, &items, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListSeries - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + BagTrackingItem* items = nullptr; + uint32_t num = 0; + REQUIRE(bagReadTrackingListSeries(nullptr, 1, &items, &num) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("ReadTrackingListSeries - NULL items returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t num = 0; + CHECK(bagReadTrackingListSeries(handle, 1, nullptr, &num) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListSeries - NULL numItems returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][tracking][errors]") { + BagHandle* handle = openSampleBag(); + BagTrackingItem* items = nullptr; + CHECK(bagReadTrackingListSeries(handle, 1, &items, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("ReadTrackingListSeries - Happy path", "[bag_c_api][tracking][happy]") { + BagHandle* handle = openSampleBag(); + BagTrackingItem* items = nullptr; + uint32_t num = 999; + CHECK(bagReadTrackingListSeries(handle, 1, &items, &num) == BAG_SUCCESS); + if (num > 0 && items) delete[] items; + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteTrackingListItem - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + REQUIRE(bagWriteTrackingListItem(nullptr, nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("WriteTrackingListItem - NULL item returns BAG_SUCCESS", "[bag_c_api][tracking][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagWriteTrackingListItem(handle, nullptr) == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("WriteTrackingListItem - Happy path", "[bag_c_api][tracking][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagTrackingItem item{}; + item.row = 0; + item.col = 0; + item.track_code = 1; + item.list_series = 1; + BagError err = bagWriteTrackingListItem(handle, &item); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SortTrackingListByNode - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + REQUIRE(bagSortTrackingListByNode(nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("SortTrackingListByNode - Happy path", "[bag_c_api][tracking][sort][node][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagError err = bagSortTrackingListByNode(handle); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SortTrackingListBySeries - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + REQUIRE(bagSortTrackingListBySeries(nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("SortTrackingListBySeries - Happy path", "[bag_c_api][tracking][sort][series][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagError err = bagSortTrackingListBySeries(handle); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SortTrackingListByCode - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][tracking][errors]") { + REQUIRE(bagSortTrackingListByCode(nullptr) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("SortTrackingListByCode - Happy path", "[bag_c_api][tracking][sort][code][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagError err = bagSortTrackingListByCode(handle); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// METADATA +// ============================================================================= + +TEST_CASE("GetMetaData - NULL handle returns nullptr", "[bag_c_api][metadata][errors]") { + CHECK(bagGetMetaData(nullptr) == nullptr); +} + +TEST_CASE("GetMetaData - Happy path", "[bag_c_api][metadata][happy]") { + BagHandle* handle = openSampleBag(); + const BagMetadata* meta = bagGetMetaData(handle); + CHECK(meta != nullptr); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetHomeFolder - NULL folder returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][metadata][errors]") { + REQUIRE(bagSetHomeFolder(nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); +} + +TEST_CASE("SetHomeFolder - Valid folder returns BAG_SUCCESS", "[bag_c_api][metadata][happy]") { + if (TestUtils::CI()) SKIP("In CI, where this test causes failures elsewhere, skipping..."); + // Before setting the home folder, see if another test has set it, if so store it so that we can reset. + auto existing = BAG::bagGetHomeFolder(); + CHECK(bagSetHomeFolder("/tmp/test_metadata_folder") == BAG_SUCCESS); + if (!existing.empty()) + { + // Reset previous home folder so that other tests won't fail. + REQUIRE(bagSetHomeFolder(existing.c_str()) == BAG_SUCCESS); + } +} + +// ============================================================================= +// GEOREF METADATA LAYER +// ============================================================================= + +TEST_CASE("CreateGeorefMetadataLayer - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + FieldDefinition def{}; + REQUIRE(bagCreateGeorefMetadataLayer(nullptr, DT_UINT32, GEOREF_METADATA_PROFILE::UNKNOWN_METADATA_PROFILE, + "elevation", &def, 1) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("CreateGeorefMetadataLayer - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + FieldDefinition def{}; + CHECK(bagCreateGeorefMetadataLayer(handle, DT_UINT32, GEOREF_METADATA_PROFILE::UNKNOWN_METADATA_PROFILE, + nullptr, &def, 1) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateGeorefMetadataLayer - NULL definition returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagCreateGeorefMetadataLayer(handle, DT_UINT32, GEOREF_METADATA_PROFILE::UNKNOWN_METADATA_PROFILE, + "elevation", nullptr, 1) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateGeorefMetadataLayer - numFields=0 returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + FieldDefinition def{}; + CHECK(bagCreateGeorefMetadataLayer(handle, DT_UINT32, GEOREF_METADATA_PROFILE::UNKNOWN_METADATA_PROFILE, + "elevation", &def, 0) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateGeorefMetadataLayer - create for nonexistant layer type returns BAG_SIMPLE_LAYER_MISSING", "[bag_c_api][georef_meta][errors][nonExistantLayerType]") { + const TestUtils::RandomFileGuard tmpFileName; + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + TestUtils::copyFile(bagFileName, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + FieldDefinition def{}; + BagError err = bagCreateGeorefMetadataLayer(handle, DT_UINT32, GEOREF_METADATA_PROFILE::UNKNOWN_METADATA_PROFILE, + "nonexistent_layer", &def, 1); + CHECK(err == BAG_HDF_CREATE_DATASET_FAILURE); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateMetadataProfileGeorefMetadataLayer - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + REQUIRE(bagCreateMetadataProfileGeorefMetadataLayer(nullptr, DT_UINT32, + GEOREF_METADATA_PROFILE::UNKNOWN_METADATA_PROFILE, "elevation") == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("CreateMetadataProfileGeorefMetadataLayer - Unknown georef metadata profile", "[bag_c_api][georef_meta][errors][unknownGeoRefMDProfile]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagError err = bagCreateMetadataProfileGeorefMetadataLayer(handle, DT_UINT32, + UNKNOWN_METADATA_PROFILE, "nonexistent_layer"); + CHECK(err == BAG_GEOREF_METADATA_LAYER_PROFILE_UNKNOWN); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("CreateMetadataProfileGeorefMetadataLayer - Preposterous layer", "[bag_c_api][georef_meta][layer][errors][preposterousLayer]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagError err = bagCreateMetadataProfileGeorefMetadataLayer(handle, DT_UINT32, + NOAA_OCS_2022_10_METADATA_PROFILE, "nonexistent_layer"); + CHECK(err == BAG_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerDefinition - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + FieldDefinition* def = nullptr; + uint32_t num = 0; + REQUIRE(bagGetGeorefMetadataLayerDefinition(nullptr, "elevation", &def, &num) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeorefMetadataLayerDefinition - NULL definition returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t num = 0; + CHECK(bagGetGeorefMetadataLayerDefinition(handle, "elevation", nullptr, &num) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerDefinition - NULL numFields returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + FieldDefinition* def = nullptr; + CHECK(bagGetGeorefMetadataLayerDefinition(handle, "elevation", &def, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerDefinition - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + FieldDefinition* def = nullptr; + uint32_t num = 0; + CHECK(bagGetGeorefMetadataLayerDefinition(handle, "nonexistent_georef_layer", &def, &num) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerRecords - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + BagCompoundDataType** records = nullptr; + uint32_t numRec = 0, numFields = 0; + REQUIRE(bagGetGeorefMetadataLayerRecords(nullptr, "elevation", &records, &numRec, &numFields) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeorefMetadataLayerRecords - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + BagCompoundDataType** records = nullptr; + uint32_t numRec = 0, numFields = 0; + CHECK(bagGetGeorefMetadataLayerRecords(handle, nullptr, &records, &numRec, &numFields) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerRecords - NULL records returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t numRec = 0, numFields = 0; + CHECK(bagGetGeorefMetadataLayerRecords(handle, "elevation", nullptr, &numRec, &numFields) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerRecords - NULL numRecords returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + BagCompoundDataType** records = nullptr; + uint32_t numFields = 0; + CHECK(bagGetGeorefMetadataLayerRecords(handle, "elevation", &records, nullptr, &numFields) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerRecords - NULL numFields returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + BagCompoundDataType** records = nullptr; + uint32_t numRec = 0; + CHECK(bagGetGeorefMetadataLayerRecords(handle, "elevation", &records, &numRec, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerRecords - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + BagCompoundDataType** records = nullptr; + uint32_t numRec = 0, numFields = 0; + CHECK(bagGetGeorefMetadataLayerRecords(handle, "nonexistent_georef_layer", &records, &numRec, &numFields) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerValueByName - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + BagCompoundDataType val{}; + REQUIRE(bagGetGeorefMetadataLayerValueByName(nullptr, "elevation", 1, "field", &val) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeorefMetadataLayerValueByName - NULL value returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetGeorefMetadataLayerValueByName(handle, "elevation", 1, "field", nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerValueByName - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + BagCompoundDataType val{}; + CHECK(bagGetGeorefMetadataLayerValueByName(handle, "nonexistent", 1, "field", &val) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerValueByIndex - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + BagCompoundDataType val{}; + REQUIRE(bagGetGeorefMetadataLayerValueByIndex(nullptr, "elevation", 1, 0, &val) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeorefMetadataLayerValueByIndex - NULL value returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetGeorefMetadataLayerValueByIndex(handle, "elevation", 1, 0, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerValueByIndex - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + BagCompoundDataType val{}; + CHECK(bagGetGeorefMetadataLayerValueByIndex(handle, "nonexistent", 1, 0, &val) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldIndex - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + uint32_t idx = 0; + REQUIRE(bagGetGeorefMetadataLayerFieldIndex(nullptr, "elevation", "field", &idx) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeorefMetadataLayerFieldIndex - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t idx = 0; + CHECK(bagGetGeorefMetadataLayerFieldIndex(handle, nullptr, "field", &idx) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldIndex - NULL fieldName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t idx = 0; + CHECK(bagGetGeorefMetadataLayerFieldIndex(handle, "elevation", nullptr, &idx) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldIndex - NULL fieldIndex returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetGeorefMetadataLayerFieldIndex(handle, "elevation", "field", nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldIndex - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t idx = 0; + CHECK(bagGetGeorefMetadataLayerFieldIndex(handle, "nonexistent", "field", &idx) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldName - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + const char* name = nullptr; + REQUIRE(bagGetGeorefMetadataLayerFieldName(nullptr, "elevation", 0, &name) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("GetGeorefMetadataLayerFieldName - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + const char* name = nullptr; + CHECK(bagGetGeorefMetadataLayerFieldName(handle, nullptr, 0, &name) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldName - NULL fieldName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + CHECK(bagGetGeorefMetadataLayerFieldName(handle, "elevation", 0, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("GetGeorefMetadataLayerFieldName - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + BagHandle* handle = openSampleBag(); + const char* name = nullptr; + CHECK(bagGetGeorefMetadataLayerFieldName(handle, "nonexistent", 0, &name) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecord - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + BagCompoundDataType rec{}; + uint32_t idx = 0; + REQUIRE(bagAddGeorefMetadataLayerRecord(nullptr, "elevation", &rec, 1, &idx) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("AddGeorefMetadataLayerRecord - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType rec{}; + uint32_t idx = 0; + CHECK(bagAddGeorefMetadataLayerRecord(handle, nullptr, &rec, 1, &idx) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecord - NULL record returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + uint32_t idx = 0; + CHECK(bagAddGeorefMetadataLayerRecord(handle, "elevation", nullptr, 1, &idx) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecord - NULL recordIndex returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType rec{}; + CHECK(bagAddGeorefMetadataLayerRecord(handle, "elevation", &rec, 1, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecord - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType rec{}; + uint32_t idx = 0; + CHECK(bagAddGeorefMetadataLayerRecord(handle, "nonexistent", &rec, 1, &idx) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecords - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + REQUIRE(bagAddGeorefMetadataLayerRecords(nullptr, "elevation", nullptr, 1, 1) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("AddGeorefMetadataLayerRecords - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagAddGeorefMetadataLayerRecords(handle, nullptr, nullptr, 1, 1) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecords - NULL records returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagAddGeorefMetadataLayerRecords(handle, "elevation", nullptr, 1, 1) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("AddGeorefMetadataLayerRecords - null records", "[bag_c_api][georef_meta][rec][errors][nullRecords]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagAddGeorefMetadataLayerRecords(handle, "nonexistent", nullptr, 1, 1) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByName - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + BagCompoundDataType val{}; + REQUIRE(bagGeorefMetadataLayerSetValueByName(nullptr, "elevation", 1, "field", &val) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("SetValueByName - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType val{}; + CHECK(bagGeorefMetadataLayerSetValueByName(handle, nullptr, 1, "field", &val) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByName - NULL fieldName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType val{}; + CHECK(bagGeorefMetadataLayerSetValueByName(handle, "elevation", 1, nullptr, &val) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByName - NULL value returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagGeorefMetadataLayerSetValueByName(handle, "elevation", 1, "field", nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByName - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType val{}; + CHECK(bagGeorefMetadataLayerSetValueByName(handle, "nonexistent", 1, "field", &val) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByIndex - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][georef_meta][errors]") { + BagCompoundDataType val{}; + REQUIRE(bagGeorefMetadataLayerSetValueByIndex(nullptr, "elevation", 1, 0, &val) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("SetValueByIndex - NULL layerName returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType val{}; + CHECK(bagGeorefMetadataLayerSetValueByIndex(handle, nullptr, 1, 0, &val) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByIndex - NULL value returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + CHECK(bagGeorefMetadataLayerSetValueByIndex(handle, "elevation", 1, 0, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("SetValueByIndex - Layer missing returns BAG_GEOREF_METADATA_LAYER_MISSING", "[bag_c_api][georef_meta][errors]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagCompoundDataType val{}; + CHECK(bagGeorefMetadataLayerSetValueByIndex(handle, "nonexistent", 1, 0, &val) == BAG_GEOREF_METADATA_LAYER_MISSING); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("bagGetGeorefMetadata - Happy Path", "[bag_c_api][georef_meta][happy]") +{ + // Forward declare stack variables for later use (we have to do this above calls to goto cleanup). + float *corrected = nullptr; + std::string datum1 = "MLLW"; + std::string datum2 = "Ellipsoid"; + std::string datum3 = "NAVD88"; + FieldDefinition defn; + FieldDefinition *defnRead = nullptr; + uint32_t numFields = 0; + + bool success = true; + const TestUtils::RandomFileGuard tmpFile; + auto tmpFileName = tmpFile.m_fileName; + BagHandle* handle = nullptr; + auto mdBuff = const_cast(reinterpret_cast(kMetadataXML.data())); + // Create new BAG + BagError err = bagCreateFromBuffer(&handle, tmpFileName.c_str(), mdBuff, + static_cast(kMetadataXML.size())); + REQUIRE(err == BAG_SUCCESS); + // Write some elevation data so that we can read corrected data below + const uint32_t kGridSize = 100; + auto *surf = static_cast(malloc(sizeof(float) * kGridSize * kGridSize)); + // Write the data. + constexpr uint32_t columnStart = 0; + constexpr uint32_t columnEnd = kGridSize - 1; + for(uint32_t row=0; row(kGridSize)); + } + } + err = bagWrite(handle, 0, columnStart, 0, columnEnd, + Elevation, "elevation", reinterpret_cast(surf)); + if (err != BAG_SUCCESS) { + success = false; + goto cleanup; + } + + defn = {"dummy_int", DT_UINT32}; + err = bagCreateGeorefMetadataLayer(handle, DT_UINT16, UNKNOWN_METADATA_PROFILE, "elevation", &defn, 1); + if (err != BAG_SUCCESS) + { + success = false; + goto cleanup; + } + + err = bagGetGeorefMetadataLayerDefinition(handle, "elevation", &defnRead, &numFields); + if (err != BAG_SUCCESS) + { + success = false; + goto cleanup; + } + CHECK(numFields == 1); + CHECK(defnRead->name == defn.name); + CHECK(defnRead->type == defn.type); + + cleanup: + free(surf); + if (corrected) free(corrected); + if (handle) REQUIRE(bagFileClose(handle) == BAG_SUCCESS); + REQUIRE(success); +} + +// ============================================================================= +// VARIABLE RESOLUTION LAYERS +// ============================================================================= + +TEST_CASE("CreateVRLayers - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][vr][errors]") { + REQUIRE(bagCreateVRLayers(nullptr, true) == BAG_INVALID_BAG_HANDLE); + REQUIRE(bagCreateVRLayers(nullptr, false) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("CreateVRLayers - Happy Path", "[bag_c_api][vr][create][happy]") { + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(std::string{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}, tmpFileName); + BagHandle* handle = openBagRW(tmpFileName); + BagError err = bagCreateVRLayers(handle, true); + CHECK(err == BAG_SUCCESS); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +// ============================================================================= +// VR METADATA +// ============================================================================= + +TEST_CASE("VRMetadataGetMinDimensions - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][vr][metadata][errors]") { + uint32_t minX, minY; + REQUIRE(bagVRMetadataGetMinDimensions(nullptr, &minX, &minY) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("VRMetadataGetMinDimensions - NULL minX returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][vr][metadata][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t minY; + CHECK(bagVRMetadataGetMinDimensions(handle, nullptr, &minY) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMinDimensions - NULL minY returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][vr][metadata][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t minX; + CHECK(bagVRMetadataGetMinDimensions(handle, &minX, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMinDimensions - No VR metadata returns BAG_HDF_DATASET_OPEN_FAILURE", "[bag_c_api][vr][metadata][errors][noVRMeta]") { + BagHandle* handle = openSampleBag(); + uint32_t minX, minY; + BagError err = bagVRMetadataGetMinDimensions(handle, &minX, &minY); + CHECK(err == BAG_HDF_DATASET_OPEN_FAILURE ); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMaxDimensions - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][vr][metadata][errors]") { + uint32_t maxX, maxY; + REQUIRE(bagVRMetadataGetMaxDimensions(nullptr, &maxX, &maxY) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("VRMetadataGetMaxDimensions - NULL maxX returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][vr][metadata][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t maxY; + CHECK(bagVRMetadataGetMaxDimensions(handle, nullptr, &maxY) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMaxDimensions - NULL maxY returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][vr][metadata][errors]") { + BagHandle* handle = openSampleBag(); + uint32_t maxX; + CHECK(bagVRMetadataGetMaxDimensions(handle, &maxX, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMaxDimensions - No VR metadata returns BAG_HDF_DATASET_OPEN_FAILURE", "[bag_c_api][vr][metadata][maxDim][errors][noVRMeta]") { + BagHandle* handle = openSampleBag(); + uint32_t maxX, maxY; + BagError err = bagVRMetadataGetMaxDimensions(handle, &maxX, &maxY); + CHECK(err == BAG_HDF_DATASET_OPEN_FAILURE); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMinResolution - NULL handle returns BAG_INVALID_BAG_HANDLE", "[bag_c_api][vr][metadata][errors]") { + float minX, minY; + REQUIRE(bagVRMetadataGetMinResolution(nullptr, &minX, &minY) == BAG_INVALID_BAG_HANDLE); +} + +TEST_CASE("VRMetadataGetMinResolution - NULL minX returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][vr][metadata][errors]") { + BagHandle* handle = openSampleBag(); + float minY; + CHECK(bagVRMetadataGetMinResolution(handle, nullptr, &minY) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} + +TEST_CASE("VRMetadataGetMinResolution - NULL minY returns BAG_INVALID_FUNCTION_ARGUMENT", "[bag_c_api][vr][metadata][errors]") { + BagHandle* handle = openSampleBag(); + float minX; + CHECK(bagVRMetadataGetMinResolution(handle, &minX, nullptr) == BAG_INVALID_FUNCTION_ARGUMENT); + REQUIRE(bagFileClose(handle) == BAG_SUCCESS); +} diff --git a/tests/test_bag_compounddatatype.cpp b/tests/test_bag_compounddatatype.cpp index be5f01e736..51f5f430de 100644 --- a/tests/test_bag_compounddatatype.cpp +++ b/tests/test_bag_compounddatatype.cpp @@ -64,6 +64,7 @@ TEST_CASE("test compound data type uint32_t creation", // CompoundDataType& operator=(uint32_t rhs) noexcept // CompoundDataType& operator=(bool rhs) noexcept // CompoundDataType& operator=(std::string rhs) noexcept +// CompoundDataType& operator=(CompoundDataType&& rhs) // template // T BAG::get(const CompondDataType& cdt) TEST_CASE("test compound data type assignment operators", @@ -81,6 +82,18 @@ TEST_CASE("test compound data type assignment operators", UNSCOPED_INFO("Check getting the float value via BAG::get() works."); CHECK(BAG::get(cdt) == kExpectedValue); + + // Cover assigning to self + auto *cdt_p = &cdt; + cdt = *cdt_p; + CHECK(BAG::get(cdt) == kExpectedValue); + CHECK(cdt.getType() == DT_FLOAT32); + + // Cover move assignment + CompoundDataType dst; + CompoundDataType src{kExpectedValue}; + dst = std::move(src); + CHECK(dst.asFloat() == kExpectedValue); } { @@ -91,6 +104,12 @@ TEST_CASE("test compound data type assignment operators", UNSCOPED_INFO("Check getting the float value via BAG::get() works."); CHECK(BAG::get(cdt) == kExpectedValue); + + // Cover move assignment + CompoundDataType dst; + CompoundDataType src{kExpectedValue}; + dst = std::move(src); + CHECK(dst.asUInt32() == kExpectedValue); } { @@ -101,6 +120,12 @@ TEST_CASE("test compound data type assignment operators", UNSCOPED_INFO("Check getting the bool value via BAG::get() works."); CHECK(BAG::get(cdt) == kExpectedValue); + + // Cover move assignment + CompoundDataType dst; + CompoundDataType src{kExpectedValue}; + dst = std::move(src); + CHECK(dst.asBool() == kExpectedValue); } { @@ -111,6 +136,19 @@ TEST_CASE("test compound data type assignment operators", UNSCOPED_INFO("Check getting the string value via BAG::get() works."); CHECK(BAG::get(cdt) == kExpectedValue); + + // Cover move assignment + CompoundDataType dst; + CompoundDataType src{kExpectedValue}; + dst = std::move(src); + CHECK(dst.asString() == kExpectedValue); + } + + { + UNSCOPED_INFO("Check using the equality operator on two different types"); + const CompoundDataType cdtf{42.23f}; + const CompoundDataType cdtu{42u}; + CHECK(cdtf != cdtu); } } @@ -143,6 +181,7 @@ TEST_CASE("test compound data type invalid cases", constexpr float kExpectedValue = 123.456f; cdt = kExpectedValue; REQUIRE_THROWS(BAG::get(cdt)); + REQUIRE_THROWS_AS(cdt.asString(), BAG::InvalidType); } { @@ -150,5 +189,6 @@ TEST_CASE("test compound data type invalid cases", constexpr uint32_t kExpectedValue = 101; cdt = kExpectedValue; REQUIRE_THROWS(BAG::get(cdt) == kExpectedValue); + REQUIRE_THROWS_AS(BAG::get(cdt), BAG::InvalidType); } } diff --git a/tests/test_bag_dataset.cpp b/tests/test_bag_dataset.cpp index 982a27deb1..3cc6d65a34 100644 --- a/tests/test_bag_dataset.cpp +++ b/tests/test_bag_dataset.cpp @@ -1,581 +1,658 @@ - -#include "test_utils.h" -#include - -#include -#include // std::getenv -#include - - -using Catch::Approx; -using BAG::Dataset; - -namespace { - -const std::string kMetadataXML{R"( - - - Unique Identifier - - - eng - - - utf8 - - - dataset - - - - - Name of individual responsible for the BAG - - - pointOfContact - - - - - 2012-01-27 - - - ISO 19115 - - - 2003/Cor.1:2006 - - - - - 2 - - - - - row - - - 100 - - - 10 - - - - - - - column - - - 100 - - - 10 - - - - - point - - - 1 - - - 0 - - - - 687910.000000,5554620.000000 691590.000000,5562100.000000 - - - - center - - - - - - - - - PROJCS["UTM-19N-Nad83", - GEOGCS["unnamed", - DATUM["North_American_Datum_1983", - SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], - TOWGS84[0,0,0,0,0,0,0]], - PRIMEM["Greenwich",0], - UNIT["degree",0.0174532925199433], - EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], - EXTENSION["Source","CARIS"]], - PROJECTION["Transverse_Mercator"], - PARAMETER["latitude_of_origin",0], - PARAMETER["central_meridian",-69], - PARAMETER["scale_factor",0.9996], - PARAMETER["false_easting",500000], - PARAMETER["false_northing",0], - UNIT["metre",1]] - - - WKT - - - - - - - - - - - VERT_CS["Alicante height", - VERT_DATUM["Alicante",2000]] - - - WKT - - - - - - - - - - - Name of dataset input - - - - - 2008-10-21 - - - creation - - - - - - - Person responsible for input data - - - originator - - - - - - - Sample Metadata - - - completed - - - grid - - - eng - - - utf8 - - - elevation - - - - - - - -66.371629 - - - -66.316454 - - - 50.114053 - - - 50.180077 - - - - - - - rawStdDev - - - trueDepth - - - cube - - - product - - - - - - - - - dataset - - - - - - - - - List to be determined by WG. I.e. Product Creation - - - 2008-10-21T12:21:53 - - - - - Name of the processor - - - processor - - - - - - - Source - - - - - Name of dataset input - - - - - - creation - - - - - - - - - 1 - - - - - - - - - - - otherRestrictions - - - some other constraints - - - - - - - unclassified - - - some user node - - - - -)"}; - -} // namespace - -// static std::shared_ptr open(const std::string &fileName, -// OpenMode openMode); -TEST_CASE("test dataset reading", "[dataset][open][getLayerTypes][createLayer]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - const size_t kNumExpectedLayers = 4; // Elevation, Uncertainty, Nominal_Elevation, Vertical Datum Corrections - - SECTION("open read only") - { - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); - REQUIRE(dataset); - - CHECK(dataset->getLayerTypes().size() == kNumExpectedLayers); - } - SECTION("open read write") - { - // Copy sample.bag and modify it. - const TestUtils::RandomFileGuard tmpFileName; - TestUtils::copyFile(bagFileName, tmpFileName); - size_t numLayers = 0; - - { // Add a new layer to the dataset. - // Scoped so the dataset is destroyed (saves at that point). - const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - const auto numLayerTypes = dataset->getLayerTypes().size(); - CHECK(numLayerTypes == kNumExpectedLayers); - - constexpr uint64_t chunkSize = 100; - constexpr int compressionLevel = 6; - - /*auto& layer = */ dataset->createSimpleLayer(Average_Elevation, - chunkSize, compressionLevel); - - numLayers = dataset->getLayerTypes().size(); - REQUIRE(numLayers == (numLayerTypes + 1)); - } - - // Read the saved BAG. - const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); - REQUIRE(dataset); - - REQUIRE(dataset->getLayerTypes().size() == numLayers); - } -} - -// static std::shared_ptr create(const std::string &fileName, -// const Metadata& metadata); -TEST_CASE("test dataset creation", "[dataset][create][getLayerTypes][open]") -{ - const TestUtils::RandomFileGuard tmpFileName; - - constexpr size_t kNumExpectedLayers = 2; // Elevation, Uncertainty - { - BAG::Metadata metadata; - metadata.loadFromBuffer(kMetadataXML); - - constexpr uint64_t chunkSize = 100; - constexpr int compressionLevel = 6; - - const auto dataset = Dataset::create(tmpFileName, std::move(metadata), - chunkSize, compressionLevel); - - REQUIRE(dataset); - REQUIRE(dataset->getLayerTypes().size() == kNumExpectedLayers); - } - - REQUIRE_NOTHROW(Dataset::open(tmpFileName, BAG_OPEN_READONLY)); - const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); - - REQUIRE(dataset); - REQUIRE(dataset->getLayerTypes().size() == kNumExpectedLayers); -} - -// std::vector getLayerTypes() const; -TEST_CASE("test get layer types", "[dataset][open][getLayerTypes]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/NAVO_data/JD211_Public_Release_1-5.bag"}; - - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); - REQUIRE(dataset); - - CHECK(dataset->getLayerTypes().size() == 2); -} - -// Layer& createLayer(LayerType type); -TEST_CASE("test add layer", "[dataset][open][createLayer][getLayerTypes]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Copy sample.bag and modify it. - const TestUtils::RandomFileGuard tmpFileName; - TestUtils::copyFile(bagFileName, tmpFileName); - - constexpr size_t kNumExpectedLayers = 4; // Elevation, Uncertainty, Nominal_Elevation, Vertical Datum Corrections - - { // Add a new layer to the dataset. - // Scoped so the dataset is destroyed (saves at that point). - const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - CHECK(dataset->getLayerTypes().size() == kNumExpectedLayers); - - constexpr uint64_t chunkSize = 100; - constexpr int compressionLevel = 6; - - /*auto& layer = */ dataset->createSimpleLayer(Average_Elevation, chunkSize, - compressionLevel); - - REQUIRE(dataset->getLayerTypes().size() == (kNumExpectedLayers + 1)); - } - - // Read the saved BAG. - const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - REQUIRE(dataset->getLayerTypes().size() == (kNumExpectedLayers + 1)); -} - -// Layer& getLayer(LayerType type); -// const Layer& getLayer(LayerType type) const; -TEST_CASE("test get layer", "[dataset][open][getLayer][getLayers]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Test the non-const method. - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - constexpr size_t kNumExpectedLayers = 4; // Elevation, Uncertainty, Nominal_Elevation, Vertical Datum Corrections - - { - const auto layers = dataset->getLayers(); - CHECK(kNumExpectedLayers == layers.size()); - - for (auto&& layer : layers) - { - const auto& layerFromId = - dataset->getLayer(layer->getDescriptor()->getId()); - CHECK(layerFromId.getDescriptor()->getInternalPath() == - layer->getDescriptor()->getInternalPath()); - } - } - - // Test the const method. - const std::shared_ptr constDataset{dataset}; - REQUIRE(constDataset); - - { - const auto layers = constDataset->getLayers(); - CHECK(kNumExpectedLayers == layers.size()); - - for (auto&& layer : layers) - { - const auto& layerFromId = - constDataset->getLayer(layer->getDescriptor()->getId()); - CHECK(layerFromId.getDescriptor()->getInternalPath() == - layer->getDescriptor()->getInternalPath()); - } - } -} - -// TrackingList& getTrackingList(); -// const TrackingList& getTrackingList() const; -TEST_CASE("test get tracking list", "[dataset][open][getTrackingList]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Test the non-const method. - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - { - REQUIRE_NOTHROW(dataset->getTrackingList()); - auto& trackingList = dataset->getTrackingList(); - CHECK(trackingList.empty()); - } - - // Test the const method. - const std::shared_ptr constDataset{dataset}; - REQUIRE(constDataset); - - { - REQUIRE_NOTHROW(constDataset->getTrackingList()); - const auto& trackingList = constDataset->getTrackingList(); - CHECK(trackingList.size() == 0); - } -} - -// const Metadata& getMetadata() const; -TEST_CASE("test get metadata", "[dataset][open][getMetadata]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Test the non-const method. - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - constexpr double kExpectedLLcornerX = 687910.; - constexpr double kExpectedLLcornerY = 5554620.; - - REQUIRE_NOTHROW(dataset->getMetadata()); - const auto& metadata = dataset->getMetadata(); - - CHECK(metadata.llCornerX() == Approx(kExpectedLLcornerX)); - CHECK(metadata.llCornerY() == Approx(kExpectedLLcornerY)); -} - -// std::tuple gridToGeo(uint32_t row, uint32_t column) const; -TEST_CASE("test grid to geo", "[dataset][open][gridToGeo]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Test the non-const method. - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - double x = 0.; - double y = 0.; - - std::tie(x, y) = dataset->gridToGeo(0, 0); - - CHECK(x == Approx(687910.0)); - CHECK(y == Approx(5554620.0)); -} - -// std::tuple geoToGrid(double x, double y) const; -TEST_CASE("test geo to grid", "[dataset][open][geoToGrid]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Test the non-const method. - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - uint64_t row = 123; - uint64_t column = 456; - - std::tie(row, column) = dataset->geoToGrid(687910.0, 5554620.0); - - CHECK(row == 0); - CHECK(column == 0); -} - -// const Descriptor& getDescriptor() const noexcept; -TEST_CASE("test get descriptor", "[dataset][getDescriptor]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - // Test the non-const method. - const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); - REQUIRE(dataset); - - const auto& descriptor = dataset->getDescriptor(); - - CHECK(descriptor.isReadOnly() == false); -} + +#include "test_utils.h" +#include + +#include +#include // std::getenv +#include + + +using Catch::Approx; +using BAG::Dataset; + +namespace { + +const std::string kMetadataXML{R"( + + + Unique Identifier + + + eng + + + utf8 + + + dataset + + + + + Name of individual responsible for the BAG + + + pointOfContact + + + + + 2012-01-27 + + + ISO 19115 + + + 2003/Cor.1:2006 + + + + + 2 + + + + + row + + + 100 + + + 10 + + + + + + + column + + + 100 + + + 10 + + + + + point + + + 1 + + + 0 + + + + 687910.000000,5554620.000000 691590.000000,5562100.000000 + + + + center + + + + + + + + + PROJCS["UTM-19N-Nad83", + GEOGCS["unnamed", + DATUM["North_American_Datum_1983", + SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], + TOWGS84[0,0,0,0,0,0,0]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433], + EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], + EXTENSION["Source","CARIS"]], + PROJECTION["Transverse_Mercator"], + PARAMETER["latitude_of_origin",0], + PARAMETER["central_meridian",-69], + PARAMETER["scale_factor",0.9996], + PARAMETER["false_easting",500000], + PARAMETER["false_northing",0], + UNIT["metre",1]] + + + WKT + + + + + + + + + + + VERT_CS["Alicante height", + VERT_DATUM["Alicante",2000]] + + + WKT + + + + + + + + + + + Name of dataset input + + + + + 2008-10-21 + + + creation + + + + + + + Person responsible for input data + + + originator + + + + + + + Sample Metadata + + + completed + + + grid + + + eng + + + utf8 + + + elevation + + + + + + + -66.371629 + + + -66.316454 + + + 50.114053 + + + 50.180077 + + + + + + + rawStdDev + + + trueDepth + + + cube + + + product + + + + + + + + + dataset + + + + + + + + + List to be determined by WG. I.e. Product Creation + + + 2008-10-21T12:21:53 + + + + + Name of the processor + + + processor + + + + + + + Source + + + + + Name of dataset input + + + + + + creation + + + + + + + + + 1 + + + + + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + + +)"}; + +} // namespace + +// static std::shared_ptr open(const std::string &fileName, +// OpenMode openMode); +TEST_CASE("test dataset reading", "[dataset][open][getLayerTypes][createLayer]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + const size_t kNumExpectedLayers = 4; // Elevation, Uncertainty, Nominal_Elevation, Vertical Datum Corrections + + SECTION("open read only") + { + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + CHECK(dataset->getLayerTypes().size() == kNumExpectedLayers); + } + SECTION("open read write") + { + // Copy sample.bag and modify it. + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(bagFileName, tmpFileName); + size_t numLayers = 0; + + { // Add a new layer to the dataset. + // Scoped so the dataset is destroyed (saves at that point). + const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + const auto numLayerTypes = dataset->getLayerTypes().size(); + CHECK(numLayerTypes == kNumExpectedLayers); + + constexpr uint64_t chunkSize = 100; + constexpr int compressionLevel = 6; + + /*auto& layer = */ dataset->createSimpleLayer(Average_Elevation, + chunkSize, compressionLevel); + + numLayers = dataset->getLayerTypes().size(); + REQUIRE(numLayers == (numLayerTypes + 1)); + } + + // Read the saved BAG. + const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + REQUIRE(dataset->getLayerTypes().size() == numLayers); + } +} + +TEST_CASE("test dataset reading, invalid metadata", "[dataset][open][invalidMetadata]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample-bad-md.bag"}; + + REQUIRE_THROWS(Dataset::open(bagFileName, BAG_OPEN_READONLY)); +} + +TEST_CASE("test dataset reading, invalid version", "[dataset][open][invalidVersion]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample-bad-ver.bag"}; + + REQUIRE_THROWS_AS(Dataset::open(bagFileName, BAG_OPEN_READONLY), BAG::InvalidBAGVersion); +} + +// static std::shared_ptr create(const std::string &fileName, +// const Metadata& metadata); +TEST_CASE("test dataset creation", "[dataset][create][getLayerTypes][open]") +{ + const TestUtils::RandomFileGuard tmpFileName; + + constexpr size_t kNumExpectedLayers = 2; // Elevation, Uncertainty + { + BAG::Metadata metadata; + metadata.loadFromBuffer(kMetadataXML); + + constexpr uint64_t chunkSize = 100; + constexpr int compressionLevel = 6; + + const auto dataset = Dataset::create(tmpFileName, std::move(metadata), + chunkSize, compressionLevel); + + REQUIRE(dataset); + REQUIRE(dataset->getLayerTypes().size() == kNumExpectedLayers); + } + + REQUIRE_NOTHROW(Dataset::open(tmpFileName, BAG_OPEN_READONLY)); + const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); + + REQUIRE(dataset); + REQUIRE(dataset->getLayerTypes().size() == kNumExpectedLayers); +} + +// std::vector getLayerTypes() const; +TEST_CASE("test get layer types", "[dataset][open][getLayerTypes]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/NAVO_data/JD211_Public_Release_1-5.bag"}; + + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + CHECK(dataset->getLayerTypes().size() == 2); +} + +TEST_CASE("test add features, read only dataset", "[dataset][create][addFeatures][readOnly]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + constexpr uint64_t chunkSize = 100; + constexpr int compressionLevel = 6; + REQUIRE_THROWS_AS(dataset->createSimpleLayer(Average_Elevation, chunkSize, compressionLevel), + BAG::ReadOnlyError); + REQUIRE_THROWS_AS(dataset->createSurfaceCorrections(BAG_SURFACE_GRID_EXTENTS, 3, 100, 6), + BAG::ReadOnlyError); + REQUIRE_THROWS_AS(dataset->createVR(100, 6, true), + BAG::ReadOnlyError); +} + +TEST_CASE("test add layer, layer exists", "[dataset][open][createLayer][layerExists]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + constexpr uint64_t chunkSize = 100; + constexpr int compressionLevel = 6; + REQUIRE_THROWS_AS(dataset->createSimpleLayer(Elevation, chunkSize, compressionLevel), + BAG::LayerExists); +} + +TEST_CASE("test add layer, invalid layer type", "[dataset][open][createLayer][invalidLayerType]") +{ + const TestUtils::RandomFileGuard tmpFileName; + + constexpr size_t kNumExpectedLayers = 2; // Elevation, Uncertainty + { + BAG::Metadata metadata; + metadata.loadFromBuffer(kMetadataXML); + + constexpr uint64_t chunkSize = 100; + constexpr int compressionLevel = 6; + + const auto dataset = Dataset::create(tmpFileName, std::move(metadata), + chunkSize, compressionLevel); + + REQUIRE(dataset); + REQUIRE(dataset->getLayerTypes().size() == kNumExpectedLayers); + REQUIRE_THROWS_AS(dataset->createSimpleLayer(Surface_Correction, chunkSize, compressionLevel), + BAG::UnsupportedLayerType); + } +} + +// Layer& createLayer(LayerType type); +TEST_CASE("test add layer", "[dataset][open][createLayer][getLayerTypes]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Copy sample.bag and modify it. + const TestUtils::RandomFileGuard tmpFileName; + TestUtils::copyFile(bagFileName, tmpFileName); + + constexpr size_t kNumExpectedLayers = 4; // Elevation, Uncertainty, Nominal_Elevation, Vertical Datum Corrections + + { // Add a new layer to the dataset. + // Scoped so the dataset is destroyed (saves at that point). + const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + CHECK(dataset->getLayerTypes().size() == kNumExpectedLayers); + + constexpr uint64_t chunkSize = 100; + constexpr int compressionLevel = 6; + + REQUIRE_NOTHROW(dataset->createSimpleLayer(Average_Elevation, chunkSize, compressionLevel)); + REQUIRE_THROWS_AS(dataset->createSurfaceCorrections(BAG_SURFACE_GRID_EXTENTS, 3, 100, 6), + BAG::LayerExists); + + REQUIRE(dataset->getLayerTypes().size() == (kNumExpectedLayers + 1)); + } + + // Read the saved BAG. + const auto dataset = Dataset::open(tmpFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + REQUIRE(dataset->getLayerTypes().size() == (kNumExpectedLayers + 1)); +} + +// Layer& getLayer(LayerType type); +// const Layer& getLayer(LayerType type) const; +TEST_CASE("test get layer", "[dataset][open][getLayer][getLayers]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Test the non-const method. + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + constexpr size_t kNumExpectedLayers = 4; // Elevation, Uncertainty, Nominal_Elevation, Vertical Datum Corrections + + { + const auto layers = dataset->getLayers(); + CHECK(kNumExpectedLayers == layers.size()); + + for (auto&& layer : layers) + { + const auto& layerFromId = + dataset->getLayer(layer->getDescriptor()->getId()); + CHECK(layerFromId.getDescriptor()->getInternalPath() == + layer->getDescriptor()->getInternalPath()); + } + CHECK_THROWS_AS(dataset->getLayer(42), BAG::InvalidLayerId); + } + + // Test the const method. + const std::shared_ptr constDataset{dataset}; + REQUIRE(constDataset); + + { + auto elevLayer = constDataset->getSimpleLayer(Elevation); + CHECK(elevLayer); + + const auto layers = constDataset->getLayers(); + CHECK(kNumExpectedLayers == layers.size()); + + for (auto&& layer : layers) + { + const auto& layerFromId = + constDataset->getLayer(layer->getDescriptor()->getId()); + CHECK(layerFromId.getDescriptor()->getInternalPath() == + layer->getDescriptor()->getInternalPath()); + CHECK_THROWS_AS(constDataset->getLayer(42), BAG::InvalidLayerId); + CHECK_NOTHROW(constDataset->getLayer(Nominal_Elevation, "nominal_elevation")); + } + } +} + +// TrackingList& getTrackingList(); +// const TrackingList& getTrackingList() const; +TEST_CASE("test get tracking list", "[dataset][open][getTrackingList]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Test the non-const method. + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + { + REQUIRE_NOTHROW(dataset->getTrackingList()); + auto& trackingList = dataset->getTrackingList(); + CHECK(trackingList.empty()); + } + + // Test the const method. + const std::shared_ptr constDataset{dataset}; + REQUIRE(constDataset); + + { + REQUIRE_NOTHROW(constDataset->getTrackingList()); + const auto& trackingList = constDataset->getTrackingList(); + CHECK(trackingList.size() == 0); + } +} + +// const Metadata& getMetadata() const; +TEST_CASE("test get metadata", "[dataset][open][getMetadata]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Test the non-const method. + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + constexpr double kExpectedLLcornerX = 687910.; + constexpr double kExpectedLLcornerY = 5554620.; + + REQUIRE_NOTHROW(dataset->getMetadata()); + const auto& metadata = dataset->getMetadata(); + + CHECK(metadata.llCornerX() == Approx(kExpectedLLcornerX)); + CHECK(metadata.llCornerY() == Approx(kExpectedLLcornerY)); +} + +// std::tuple gridToGeo(uint32_t row, uint32_t column) const; +TEST_CASE("test grid to geo", "[dataset][open][gridToGeo]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Test the non-const method. + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + double x = 0.; + double y = 0.; + + std::tie(x, y) = dataset->gridToGeo(0, 0); + + CHECK(x == Approx(687910.0)); + CHECK(y == Approx(5554620.0)); +} + +// std::tuple geoToGrid(double x, double y) const; +TEST_CASE("test geo to grid", "[dataset][open][geoToGrid]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Test the non-const method. + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + uint64_t row = 123; + uint64_t column = 456; + + std::tie(row, column) = dataset->geoToGrid(687910.0, 5554620.0); + + CHECK(row == 0); + CHECK(column == 0); +} + +// const Descriptor& getDescriptor() const noexcept; +TEST_CASE("test get descriptor", "[dataset][getDescriptor]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + // Test the non-const method. + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READ_WRITE); + REQUIRE(dataset); + + const auto& descriptor = dataset->getDescriptor(); + + CHECK(descriptor.isReadOnly() == false); +} diff --git a/tests/test_bag_exceptions.cpp b/tests/test_bag_exceptions.cpp new file mode 100644 index 0000000000..d2ddf23d84 --- /dev/null +++ b/tests/test_bag_exceptions.cpp @@ -0,0 +1,33 @@ +#include "test_utils.h" +#include +#include +#include + +using BAG::LayerType; + +TEST_CASE("getAttributeInfo throws UnsupportedSimpleLayerType for unknown layer type", "[exceptions]") { + // We use an invalid LayerType value to trigger the default case in getAttributeInfo + LayerType invalidType = static_cast(100); + + REQUIRE_THROWS_AS(BAG::getAttributeInfo(invalidType), BAG::UnsupportedSimpleLayerType); +} + +TEST_CASE("Dataset::open throws errors for invalid files", "[exceptions][DatasetNotFound]") { + // Testing with a non-existent file to check for DatasetNotFound (or error handling) + // Note: BagDataset::open might not throw but returns null according to its implementation + // Let's check how it handles it. + const std::string nonExistentFile = "/tmp/non_existent_file_for_bag_test"; + auto dataset = BAG::Dataset::open(nonExistentFile, BAG_OPEN_READONLY); + REQUIRE(!dataset); +} + +TEST_CASE("Dataset::getLayer throws errors for invalid id", "[exceptions][invalidId]") { + // We need a dataset to call getLayer on. + // Since we can't easily create one without files, we can't test this yet. +} + +TEST_CASE("AttributeInfo exceptions", "[exceptions][UnsupportedAttributeType]") { + // Test unsupported attribute type + // We need to find a way to trigger UnsupportedAttributeType + // It's used in some contexts. +} diff --git a/tests/test_bag_georefmetadata_layer.cpp b/tests/test_bag_georefmetadata_layer.cpp index 3bc93d47d5..a6ef170ddd 100644 --- a/tests/test_bag_georefmetadata_layer.cpp +++ b/tests/test_bag_georefmetadata_layer.cpp @@ -32,27 +32,51 @@ namespace { REQUIRE(dataset); std::string elevationLayerName = result.second; + // Test trying to create an metadata layer with a known profile, but invalid profile type + REQUIRE_THROWS_AS(dataset->createGeorefMetadataLayer(UNKNOWN_METADATA_PROFILE, + elevationLayerName, 100, 1), BAG::UknownMetadataProfile); + TestUtils::create_unknown_metadata(elevationLayerName, dataset); + dataset->close(); } // Open the dataset and read some values back { UNSCOPED_INFO("Open dataset with unknown metadata profile."); + const auto datasetRO = Dataset::open(tmpBagFileName, BAG_OPEN_READONLY); REQUIRE(datasetRO); REQUIRE(datasetRO->getLayers().size() == 3); + std::string elevationLayerName = "elevation"; + + // Cover calling createGeorefMetadataLayer in a read-only dataset + { + BAG::RecordDefinition definition(1); + definition[0].name = "dummy_int"; + definition[0].type = DT_UINT32; + REQUIRE_THROWS_AS(datasetRO->createGeorefMetadataLayer(DT_UINT16, UNKNOWN_METADATA_PROFILE, + elevationLayerName, definition, 100, + 1), BAG::ReadOnlyError); + } + + // Cover getGeorefMetadataLayer for const Datasets + const std::shared_ptr constDataset{datasetRO}; + std::shared_ptr constGeorefLayer = constDataset->getGeorefMetadataLayer(elevationLayerName); uint32_t numRows = 0; uint32_t numColumns = 0; std::tie(numRows, numColumns) = datasetRO->getDescriptor().getDims(); - std::string elevationLayerName = "elevation"; const auto& compoundLayer = datasetRO->getGeorefMetadataLayer(elevationLayerName); REQUIRE(compoundLayer); const auto& valueTable = compoundLayer->getValueTable(); + auto georefLayers = datasetRO->getGeorefMetadataLayers(); + REQUIRE(georefLayers.size() == 1); + REQUIRE(georefLayers[0].get() == compoundLayer.get()); + { // Read region of georeferenced metadata layer raster associated with first index, check values auto buff = compoundLayer->read(0, 0, 4, @@ -262,3 +286,23 @@ namespace { } } } + +TEST_CASE("test dataset multiple metadata profile creation", "[dataset][create][compoundLayer][multiple]") +{ + const std::string metadataFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.xml"}; + const TestUtils::RandomFileGuard tmpBagFileName; + + // Create BAG with NOAA NBS 2022-06 metadata profile layer and an unknown profile metadata layer + const auto result = TestUtils::createBag(metadataFileName, + tmpBagFileName); + std::shared_ptr dataset = result.first; + std::string elevationLayerName = result.second; + TestUtils::create_NOAA_OCS_Metadata(elevationLayerName, dataset); + auto uncertLayer = dataset->getSimpleLayer(Uncertainty); + TestUtils::create_unknown_metadata(uncertLayer->getDescriptor()->getName(), dataset); + REQUIRE(dataset->getLayers().size() == 4); + auto layerTypes = dataset->getLayerTypes(); + REQUIRE(layerTypes.size() == 3); + dataset->close(); +} diff --git a/tests/test_bag_hdfhelper.cpp b/tests/test_bag_hdfhelper.cpp new file mode 100644 index 0000000000..e9f64e5061 --- /dev/null +++ b/tests/test_bag_hdfhelper.cpp @@ -0,0 +1,80 @@ +#include + +#include + +#include +#include + + +TEST_CASE("test HDF helper utility code, createH5compType", "[hdfhelper][createH5compType]") +{ + H5::CompType h; + + // Node group + h = BAG::createH5compType(Hypothesis_Strength, NODE); + CHECK(h.getMemberName(0) == "hyp_strength"); + CHECK(h.getMemberArrayType(0) == H5::PredType::NATIVE_FLOAT); + h = BAG::createH5compType(Num_Hypotheses, NODE); + CHECK(h.getMemberName(0) == "num_hypotheses"); + CHECK(h.getMemberArrayType(0) == H5::PredType::NATIVE_UINT32); + + REQUIRE_THROWS_AS(BAG::createH5compType(Std_Dev, NODE), BAG::UnsupportedLayerType); + + // Elevation group + h = BAG::createH5compType(Shoal_Elevation, ELEVATION); + CHECK(h.getMemberName(0) == "shoal_elevation"); + CHECK(h.getMemberArrayType(0) == H5::PredType::NATIVE_FLOAT); + h = BAG::createH5compType(Std_Dev, ELEVATION); + CHECK(h.getMemberName(0) == "stddev"); + CHECK(h.getMemberArrayType(0) == H5::PredType::NATIVE_FLOAT); + h = BAG::createH5compType(Num_Soundings, ELEVATION); + CHECK(h.getMemberName(0) == "num_soundings"); + CHECK(h.getMemberArrayType(0) == H5::PredType::NATIVE_INT32); + REQUIRE_THROWS_AS(BAG::createH5compType(Num_Hypotheses, ELEVATION), BAG::UnsupportedLayerType); + + // Invalid group type + REQUIRE_THROWS_AS(BAG::createH5compType(Num_Hypotheses, UNKNOWN_GROUP_TYPE), BAG::UnsupportedGroupType); +} + +TEST_CASE("test HDF helper utility code, getH5fileType", "[hdfhelper][getH5fileType]") +{ + CHECK(BAG::getH5fileType(DT_UINT32) == H5::PredType::NATIVE_UINT32); + CHECK(BAG::getH5fileType(DT_FLOAT32) == H5::PredType::NATIVE_FLOAT); + CHECK(BAG::getH5fileType(DT_UINT8) == H5::PredType::NATIVE_UINT8); + CHECK(BAG::getH5fileType(DT_UINT16) == H5::PredType::NATIVE_UINT16); + CHECK(BAG::getH5fileType(DT_UINT64) == H5::PredType::NATIVE_UINT64); + CHECK(BAG::getH5fileType(DT_BOOLEAN) == H5::PredType::NATIVE_HBOOL); + CHECK(BAG::getH5fileType(DT_STRING) == H5::StrType{H5::PredType::C_S1, H5T_VARIABLE}); + REQUIRE_THROWS_AS(BAG::getH5fileType(DT_COMPOUND), BAG::UnsupportedDataType); + REQUIRE_THROWS_AS(BAG::getH5fileType(DT_UNKNOWN_DATA_TYPE), BAG::UnsupportedDataType); +} + +TEST_CASE("test HDF helper utility code, getH5memoryType", "[hdfhelper][getH5memoryType]") +{ + CHECK(BAG::getH5memoryType(DT_UINT32) == H5::PredType::NATIVE_UINT32); + CHECK(BAG::getH5memoryType(DT_FLOAT32) == H5::PredType::NATIVE_FLOAT); + CHECK(BAG::getH5memoryType(DT_UINT8) == H5::PredType::NATIVE_UINT8); + CHECK(BAG::getH5memoryType(DT_UINT16) == H5::PredType::NATIVE_UINT16); + CHECK(BAG::getH5memoryType(DT_UINT64) == H5::PredType::NATIVE_UINT64); + CHECK(BAG::getH5memoryType(DT_BOOLEAN) == H5::PredType::NATIVE_HBOOL); + CHECK(BAG::getH5memoryType(DT_STRING) == H5::StrType{H5::PredType::C_S1, H5T_VARIABLE}); + REQUIRE_THROWS_AS(BAG::getH5memoryType(DT_COMPOUND), BAG::UnsupportedDataType); + REQUIRE_THROWS_AS(BAG::getH5memoryType(DT_UNKNOWN_DATA_TYPE), BAG::UnsupportedDataType); +} + +TEST_CASE("test HDF helper utility code, createAttributes", "[hdfhelper][createAttributes]") +{ + // Use a dummy dataset to test the branch where paths are empty, the other branch is tested elsewhere. + // TODO: poss in a real dataset and make sure that calling createAttributes with an empty path has no side effects. + H5::DataSet h5dataSet = H5::DataSet(); + REQUIRE_NOTHROW(BAG::createAttributes(h5dataSet, H5::PredType::NATIVE_UINT32,{})); +} + +TEST_CASE("test HDF helper utility code, writeAttributes", "[hdfhelper][writeAttributes]") +{ + // Use a dummy dataset to test the branch where paths are empty, the other branch is tested elsewhere. + // TODO: poss in a real dataset and make sure that calling writeAttributes with an empty path has no side effects. + H5::DataSet h5dataSet = H5::DataSet(); + REQUIRE_NOTHROW(BAG::writeAttributes(h5dataSet, H5::PredType::NATIVE_UINT32, + std::numeric_limits::max(), {})); +} diff --git a/tests/test_bag_interleavedlegacylayer.cpp b/tests/test_bag_interleavedlegacylayer.cpp index 07180ce22c..3978401152 100644 --- a/tests/test_bag_interleavedlegacylayer.cpp +++ b/tests/test_bag_interleavedlegacylayer.cpp @@ -29,6 +29,15 @@ TEST_CASE("test interleaved legacy layer read", "[interleavedlegacylayer][read]" REQUIRE_NOTHROW(dataset->getLayer(kLayerType)); const auto& layer = dataset->getLayer(kLayerType); + // This layer doesn't support writing, but a call to write() is still possible, and calling it should + // not affect subsequent reads. + { + auto& mlayer = dataset->getLayer(kLayerType); + size_t bufferSize = 8; + BAG::UInt8Array b{bufferSize}; + REQUIRE_NOTHROW(mlayer.write(0, 0, 0, 0, b.data())); + } + const auto buffer = layer.read(247, 338, 248, 340); // 2x3 REQUIRE(buffer); @@ -39,7 +48,8 @@ TEST_CASE("test interleaved legacy layer read", "[interleavedlegacylayer][read]" const float* floats = reinterpret_cast(buffer.data()); - for (size_t i=0; i +#include +#include +#include + +TEST_CASE("bagCoordsys parses various WKT projection strings", "[legacy_crs][parsing]") { + // Test various projection types + REQUIRE(BAG::bagCoordsys("Mercator") == BAG::CoordinateType::Mercator); + REQUIRE(BAG::bagCoordsys("UTM") == BAG::CoordinateType::UTM); + REQUIRE(BAG::bagCoordsys("Geodetic") == BAG::CoordinateType::Geodetic); + + // Test case insensitivity and potential error handling + // (Depending on implementation, if it's noexcept, it might return Unknown) + REQUIRE(BAG::bagCoordsys("UNKNOWN_PROJECTION") == BAG::CoordinateType::Unknown); +} + +TEST_CASE("bagDatumID parses various WKT datum strings", "[legacy_crs][parsing]") { + REQUIRE(BAG::bagDatumID("WGS84") == BAG::BagDatum::wgs84); + REQUIRE(BAG::bagDatumID("WGS72") == BAG::BagDatum::wgs72); + REQUIRE(BAG::bagDatumID("NAD83") == BAG::BagDatum::nad83); + + // Test error handling for unknown datum + REQUIRE(BAG::bagDatumID("INVALID_DATUM") == BAG::BagDatum::unknown); +} + +TEST_CASE("bagLegacyToWkt handles valid conversions", "[legacy_crs][conversion][bagLegacyToWkt]") { + auto vdatums = std::vector{"North_American_Vertical_Datum_1988", // wgs84 + "National Geodetic Vertical Datum 1929", // wgs72 + "National Geodetic Vertical Datum 1929", // nad83 + "North_American_Vertical_Datum_1988", // utm + "North_American_Vertical_Datum_1988", // albers + "North_American_Vertical_Datum_1988", // azimEq + "EGM84 height", // bonne + "EGM84 height", // cassini + "EGM84 height", // cylindrical + "EGM84 height", // eckert4 + "EGM84 height", // eckert6 + "EGM84 height", // equiCyl + "EGM84 height", // gnomic + "North_American_Vertical_Datum_1988", // lambert + "EGM84 height", // miller + "EGM84 height", // mollweide + "GCS_ITRF_1988", //nzmg + "North_American_Vertical_Datum_1988", // ortho + "North_American_Vertical_Datum_1988", // polStereo + "EGM84 height", // polyconic + "EGM84 height", // sinusoidal + "North_American_Vertical_Datum_1988", // stereographic + "North_American_Vertical_Datum_1988", // transMerc + "EGM84 height", // vanDerGrinten + "UNKNOWN"}; + BAG::BagLegacyReferenceSystem wgs84; + wgs84.coordSys = BAG::CoordinateType::Geodetic; + wgs84.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem wgs72; + wgs72.coordSys = BAG::CoordinateType::Geodetic; + wgs72.geoParameters.datum = BAG::BagDatum::wgs72; + + BAG::BagLegacyReferenceSystem nad83; + nad83.coordSys = BAG::CoordinateType::Geodetic; + nad83.geoParameters.datum = BAG::BagDatum::nad83; + + BAG::BagLegacyReferenceSystem utm; + utm.coordSys = BAG::CoordinateType::UTM; + utm.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem albers; + albers.coordSys = BAG::CoordinateType::Albers_Equal_Area_Conic; + albers.geoParameters.datum = BAG::BagDatum::nad83; + + BAG::BagLegacyReferenceSystem azimEq; + azimEq.coordSys = BAG::CoordinateType::Azimuthal_Equidistant; + azimEq.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem bonne; + bonne.coordSys = BAG::CoordinateType::Bonne; + bonne.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem cassini; + cassini.coordSys = BAG::CoordinateType::Cassini; + cassini.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem cylindrical; + cylindrical.coordSys = BAG::CoordinateType::Cylindrical_Equal_Area; + cylindrical.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem eckert4; + eckert4.coordSys = BAG::CoordinateType::Eckert4; + eckert4.geoParameters.datum = BAG::BagDatum::unknown; + + BAG::BagLegacyReferenceSystem eckert6; + eckert6.coordSys = BAG::CoordinateType::Eckert6; + eckert6.geoParameters.datum = BAG::BagDatum::unknown; + + BAG::BagLegacyReferenceSystem equiCyl; + equiCyl.coordSys = BAG::CoordinateType::Equidistant_Cylindrical; + equiCyl.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem gnomic; + gnomic.coordSys = BAG::CoordinateType::Gnomonic; + gnomic.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem lambert; + lambert.coordSys = BAG::CoordinateType::Lambert_Conformal_Conic; + lambert.geoParameters.datum = BAG::BagDatum::nad83; + + BAG::BagLegacyReferenceSystem miller; + miller.coordSys = BAG::CoordinateType::Miller_Cylindrical; + miller.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem mollweide; + mollweide.coordSys = BAG::CoordinateType::Mollweide; + mollweide.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem nzmg; + nzmg.coordSys = BAG::CoordinateType::NZMG; + nzmg.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem ortho; + ortho.coordSys = BAG::CoordinateType::Orthographic; + ortho.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem polStereo; + polStereo.coordSys = BAG::CoordinateType::Polar_Stereo; + polStereo.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem polyconic; + polyconic.coordSys = BAG::CoordinateType::Polyconic; + polyconic.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem sinusoidal; + sinusoidal.coordSys = BAG::CoordinateType::Sinusoidal; + sinusoidal.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem stereographic; + stereographic.coordSys = BAG::CoordinateType::Stereographic; + stereographic.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem transMerc; + transMerc.coordSys = BAG::CoordinateType::Transverse_Mercator; + transMerc.geoParameters.datum = BAG::BagDatum::nad83; + + BAG::BagLegacyReferenceSystem vanDerGrinten; + vanDerGrinten.coordSys = BAG::CoordinateType::Van_der_Grinten; + vanDerGrinten.geoParameters.datum = BAG::BagDatum::wgs84; + + BAG::BagLegacyReferenceSystem unknown; + unknown.coordSys = BAG::CoordinateType::Mercator; + unknown.geoParameters.datum = BAG::BagDatum::unknown; + + auto systems = std::vector{wgs84, wgs72, nad83, utm, albers, azimEq, bonne, + cassini, cylindrical, eckert4, eckert6, equiCyl, gnomic, lambert, miller, mollweide, nzmg, ortho, polStereo, + polyconic, sinusoidal, stereographic, transMerc, vanDerGrinten, unknown}; + REQUIRE(vdatums.size() == systems.size()); + + for (size_t i = 0; i < vdatums.size(); i++) { + auto system = systems[i]; + auto vdatum = vdatums[i].c_str(); + strncpy(system.geoParameters.vertical_datum, vdatum, strlen(vdatum)); + system.geoParameters.scale_factor = 1.0; + system.geoParameters.central_meridian = 0.0; + system.geoParameters.latitude_of_centre = 0.0; + + char hBuffer[256] = {0}; + char vBuffer[256] = {0}; + + // Test valid conversions + BagError err = BAG::bagLegacyToWkt(system, hBuffer, sizeof(hBuffer), vBuffer, sizeof(vBuffer)); + + REQUIRE(err == BAG_SUCCESS); + REQUIRE(strlen(hBuffer) > 0); + REQUIRE(strlen(vBuffer) > 0); + } +} diff --git a/tests/test_bag_metadata.cpp b/tests/test_bag_metadata.cpp index e13f7bb5b5..d95c8245c1 100644 --- a/tests/test_bag_metadata.cpp +++ b/tests/test_bag_metadata.cpp @@ -1,422 +1,459 @@ - -#include "test_utils.h" -#include -#include - -#include -#include // std::getenv -#include // std::ofstream -#include - - -using Catch::Approx; -using BAG::Dataset; -using BAG::Metadata; - -namespace { - -//TODO Find valid v1 metadata -//const std::string kXMLv1MetadataBuffer; - -const std::string kXMLv2MetadataBuffer{R"( - - - Unique Identifier - - - eng - - - utf8 - - - dataset - - - - - Name of individual responsible for the BAG - - - pointOfContact - - - - - 2012-01-27 - - - ISO 19115 - - - 2003/Cor.1:2006 - - - - - 2 - - - - - row - - - 100 - - - 10 - - - - - - - column - - - 100 - - - 10 - - - - - point - - - 1 - - - 0 - - - - 687910.000000,5554620.000000 691590.000000,5562100.000000 - - - - center - - - - - - - - - PROJCS["UTM-19N-Nad83", - GEOGCS["unnamed", - DATUM["North_American_Datum_1983", - SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], - TOWGS84[0,0,0,0,0,0,0]], - PRIMEM["Greenwich",0], - UNIT["degree",0.0174532925199433], - EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], - EXTENSION["Source","CARIS"]], - PROJECTION["Transverse_Mercator"], - PARAMETER["latitude_of_origin",0], - PARAMETER["central_meridian",-69], - PARAMETER["scale_factor",0.9996], - PARAMETER["false_easting",500000], - PARAMETER["false_northing",0], - UNIT["metre",1]] - - - WKT - - - - - - - - - - - VERT_CS["Alicante height", - VERT_DATUM["Alicante",2000]] - - - WKT - - - - - - - - - - - Name of dataset input - - - - - 2008-10-21 - - - creation - - - - - - - Person responsible for input data - - - originator - - - - - - - Sample Metadata - - - completed - - - grid - - - eng - - - utf8 - - - elevation - - - - - - - -66.371629 - - - -66.316454 - - - 50.114053 - - - 50.180077 - - - - - - - rawStdDev - - - trueDepth - - - cube - - - product - - - - - - - - - dataset - - - - - - - - - List to be determined by WG. I.e. Product Creation - - - 2008-10-21T12:21:53 - - - - - Name of the processor - - - processor - - - - - - - Source - - - - - Name of dataset input - - - - - - creation - - - - - - - - - 1 - - - - - - - - - - - otherRestrictions - - - some other constraints - - - - - - - unclassified - - - some user node - - - -)"}; - -} // namespace - -// Metadata(); -// explicit Metadata(Dataset& dataset); -// Metadata(const Metadata& other) = delete; -// Metadata& operator=(const Metadata&) = delete; -// ~Metadata() noexcept; -TEST_CASE("test metadata construction and destruction", "[metadata][constructor][destructor]") -{ - { - // Test default constructor. - Metadata metadata; - CHECK(metadata.llCornerX() == Approx{INIT_VALUE}); - // Test destructor. - } - { - // Test constructor taking a dataset. - const std::string bagFileName{ - std::string{std::getenv("BAG_SAMPLES_PATH")} + "/sample.bag"}; - - const auto pDataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); - REQUIRE(pDataset); - - Metadata metadata(*pDataset); - CHECK(metadata.llCornerX() == Approx{687910.0}); - auto wkt = metadata.horizontalReferenceSystemAsWKT(); - CHECK(!wkt.empty()); - } -} - -// const BagMetadata& getStruct() const; -TEST_CASE("test get struct", - "[metadata][getStruct][horizontalReferenceSystemAsWKT]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - const auto pDataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); - - REQUIRE(pDataset); - REQUIRE_NOTHROW(pDataset->getMetadata()); - - const auto& metadata = pDataset->getMetadata(); - auto bagStruct = metadata.getStruct(); - REQUIRE(bagStruct.fileIdentifier); - REQUIRE(bagStruct.spatialRepresentationInfo); -} - -// std::string horizontalReferenceSystemAsWKT() const; -TEST_CASE("test horizontal reference system as WKT", - "[metadata][horizontalReferenceSystemAsWKT]") -{ - const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + - "/sample.bag"}; - - const auto pDataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); - - REQUIRE(pDataset); - REQUIRE_NOTHROW(pDataset->getMetadata()); - - const auto& metadata = pDataset->getMetadata(); - auto horizontalRefSys = metadata.horizontalReferenceSystemAsWKT(); - CHECK(!horizontalRefSys.empty()); -} - -// void loadFromFile(const std::string& fileName); -TEST_CASE("test load from file", - "[metadata][loadFromFile][columnResolution][horizontalReferenceSystemAsWKT]" - "[llCornerX][llCornerY][rowResolution][urCornerX][urCornerY]") -{ - TestUtils::RandomFileGuard tmpFileName; - - // Save the XML buffer to a file. - { - std::ofstream out(tmpFileName); - out << kXMLv2MetadataBuffer; - } - - Metadata metadata; - REQUIRE_NOTHROW(metadata.loadFromFile(tmpFileName)); - - CHECK(metadata.columnResolution() == Approx{10.0}); - CHECK(std::string{metadata.horizontalReferenceSystemAsWKT()}.empty() == false); - CHECK(metadata.llCornerX() == Approx{687910.000000}); - CHECK(metadata.llCornerY() == Approx{5554620.000000}); - CHECK(metadata.rowResolution() == Approx{10.0}); - CHECK(metadata.urCornerX() == Approx{691590.000000}); - CHECK(metadata.urCornerY() == Approx{5562100.000000}); -} - -// void loadFromBuffer(const std::string& xmlBuffer); -// double columnResolution() const; -// uint32_t columns() const noexcept; -// uint32_t rows() const noexcept; -TEST_CASE("test load from buffer", - "[metadata][loadFromBuffer][columnResolution]" - "[horizontalReferenceSystemAsWKT][llCornerX][llCornerY][rowResolution]" - "[urCornerX][urCornerY][columns][rows]") -{ - Metadata metadata; - REQUIRE_NOTHROW(metadata.loadFromBuffer(kXMLv2MetadataBuffer)); - - CHECK(metadata.columnResolution() == Approx{10.0}); - CHECK(metadata.llCornerX() == Approx{687910.000000}); - CHECK(metadata.llCornerY() == Approx{5554620.000000}); - CHECK(metadata.rowResolution() == Approx{10.0}); - CHECK(metadata.urCornerX() == Approx{691590.000000}); - CHECK(metadata.urCornerY() == Approx{5562100.000000}); - CHECK(metadata.rows() == 100); - CHECK(metadata.columns() == 100); -} - + +#include "test_utils.h" +#include +#include + +#include +#include // std::getenv +#include // std::ofstream +#include + + +using Catch::Approx; +using BAG::Dataset; +using BAG::Metadata; + +namespace { + +//TODO Find valid v1 metadata +//const std::string kXMLv1MetadataBuffer; + +const std::string kXMLv2MetadataBuffer{R"( + + + Unique Identifier + + + eng + + + utf8 + + + dataset + + + + + Name of individual responsible for the BAG + + + pointOfContact + + + + + 2012-01-27 + + + ISO 19115 + + + 2003/Cor.1:2006 + + + + + 2 + + + + + row + + + 100 + + + 10 + + + + + + + column + + + 100 + + + 10 + + + + + point + + + 1 + + + 0 + + + + 687910.000000,5554620.000000 691590.000000,5562100.000000 + + + + center + + + + + + + + + PROJCS["UTM-19N-Nad83", + GEOGCS["unnamed", + DATUM["North_American_Datum_1983", + SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], + TOWGS84[0,0,0,0,0,0,0]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433], + EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], + EXTENSION["Source","CARIS"]], + PROJECTION["Transverse_Mercator"], + PARAMETER["latitude_of_origin",0], + PARAMETER["central_meridian",-69], + PARAMETER["scale_factor",0.9996], + PARAMETER["false_easting",500000], + PARAMETER["false_northing",0], + UNIT["metre",1]] + + + WKT + + + + + + + + + + + VERT_CS["Alicante height", + VERT_DATUM["Alicante",2000]] + + + WKT + + + + + + + + + + + Name of dataset input + + + + + 2008-10-21 + + + creation + + + + + + + Person responsible for input data + + + originator + + + + + + + Sample Metadata + + + completed + + + grid + + + eng + + + utf8 + + + elevation + + + + + + + -66.371629 + + + -66.316454 + + + 50.114053 + + + 50.180077 + + + + + + + rawStdDev + + + trueDepth + + + cube + + + product + + + + + + + + + dataset + + + + + + + + + List to be determined by WG. I.e. Product Creation + + + 2008-10-21T12:21:53 + + + + + Name of the processor + + + processor + + + + + + + Source + + + + + Name of dataset input + + + + + + creation + + + + + + + + + 1 + + + + + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + +)"}; + + const std::string kXMLv2MetadataBufferInvalid{R"(This is not an XML document.\n)"}; + +} // namespace + +// Metadata(); +// explicit Metadata(Dataset& dataset); +// Metadata(const Metadata& other) = delete; +// Metadata& operator=(const Metadata&) = delete; +// ~Metadata() noexcept; +TEST_CASE("test metadata construction and destruction", "[metadata][constructor][destructor]") +{ + { + // Test default constructor. + Metadata metadata; + CHECK(metadata.llCornerX() == Approx{INIT_VALUE}); + // Test destructor. + } + { + // Test constructor taking a dataset. + const std::string bagFileName{ + std::string{std::getenv("BAG_SAMPLES_PATH")} + "/sample.bag"}; + + const auto pDataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(pDataset); + + Metadata metadata(*pDataset); + CHECK(metadata.getXMLlength() == 11005); + CHECK(metadata.llCornerX() == Approx{687910.0}); + auto wkt = metadata.horizontalReferenceSystemAsWKT(); + CHECK(!wkt.empty()); + } +} + +// const BagMetadata& getStruct() const; +TEST_CASE("test get struct", + "[metadata][getStruct][horizontalReferenceSystemAsWKT]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + const auto pDataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + + REQUIRE(pDataset); + REQUIRE_NOTHROW(pDataset->getMetadata()); + + const auto& metadata = pDataset->getMetadata(); + auto bagStruct = metadata.getStruct(); + REQUIRE(bagStruct.fileIdentifier); + REQUIRE(bagStruct.spatialRepresentationInfo); +} + +// std::string horizontalReferenceSystemAsWKT() const; +TEST_CASE("test horizontal reference system as WKT", + "[metadata][horizontalReferenceSystemAsWKT]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + const auto pDataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + + REQUIRE(pDataset); + REQUIRE_NOTHROW(pDataset->getMetadata()); + + const auto& metadata = pDataset->getMetadata(); + auto horizontalRefSys = metadata.horizontalReferenceSystemAsWKT(); + CHECK(!horizontalRefSys.empty()); +} + +// void loadFromFile(const std::string& fileName); +TEST_CASE("test load from file", + "[metadata][loadFromFile][columnResolution][horizontalReferenceSystemAsWKT]" + "[llCornerX][llCornerY][rowResolution][urCornerX][urCornerY]") +{ + TestUtils::RandomFileGuard tmpFileName; + + // Save the XML buffer to a file. + { + std::ofstream out(tmpFileName); + out << kXMLv2MetadataBuffer; + } + + Metadata metadata; + REQUIRE_NOTHROW(metadata.loadFromFile(tmpFileName)); + + CHECK(metadata.columnResolution() == Approx{10.0}); + CHECK(std::string{metadata.horizontalReferenceSystemAsWKT()}.empty() == false); + CHECK(metadata.llCornerX() == Approx{687910.000000}); + CHECK(metadata.llCornerY() == Approx{5554620.000000}); + CHECK(metadata.rowResolution() == Approx{10.0}); + CHECK(metadata.urCornerX() == Approx{691590.000000}); + CHECK(metadata.urCornerY() == Approx{5562100.000000}); +} + +// void loadFromBuffer(const std::string& xmlBuffer); +// double columnResolution() const; +// uint32_t columns() const noexcept; +// uint32_t rows() const noexcept; +TEST_CASE("test load from buffer", + "[metadata][loadFromBuffer][columnResolution]" + "[horizontalReferenceSystemAsWKT][llCornerX][llCornerY][rowResolution]" + "[urCornerX][urCornerY][columns][rows]") +{ + Metadata metadata; + REQUIRE_NOTHROW(metadata.loadFromBuffer(kXMLv2MetadataBuffer)); + + CHECK(metadata.columnResolution() == Approx{10.0}); + CHECK(metadata.llCornerX() == Approx{687910.000000}); + CHECK(metadata.llCornerY() == Approx{5554620.000000}); + CHECK(metadata.rowResolution() == Approx{10.0}); + CHECK(metadata.urCornerX() == Approx{691590.000000}); + CHECK(metadata.urCornerY() == Approx{5562100.000000}); + CHECK(metadata.rows() == 100); + CHECK(metadata.columns() == 100); +} + +TEST_CASE("test load from buffer", "[metadata][loadFromBuffer][invalid]") +{ + Metadata metadata; + REQUIRE_THROWS(metadata.loadFromBuffer(kXMLv2MetadataBufferInvalid)); +} + +TEST_CASE("test load from buffer", "[metadata][loadFromFile][invalid]") +{ + const std::string xmlFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/invalid.xml"}; + + Metadata metadata; + REQUIRE_THROWS(metadata.loadFromFile(xmlFileName)); +} + +TEST_CASE("test dataset reading, metadata", "[dataset][open][metadata]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample.bag"}; + + auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + Metadata metadata(dataset); + REQUIRE(metadata.getXMLlength() == 11005); +} + +TEST_CASE("test dataset reading, no metadata", "[dataset][open][noMetadata]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + + "/sample-no-md.bag"}; + + REQUIRE_THROWS(Dataset::open(bagFileName, BAG_OPEN_READONLY)); +} diff --git a/tests/test_bag_metadata_export.cpp b/tests/test_bag_metadata_export.cpp new file mode 100644 index 0000000000..f31e97bf2c --- /dev/null +++ b/tests/test_bag_metadata_export.cpp @@ -0,0 +1,239 @@ +#include "test_utils.h" +#include +#include +#include +#include +#include +#include + +using Catch::Approx; +using BAG::Dataset; +using BAG::Metadata; + +// Redefining the buffer from test_bag_metadata.cpp for testing purposes +const std::string kXMLv2MetadataBuffer{R"( + + + Unique Identifier + + + eng + + + utf8 + + + dataset + + + + + Name of individual responsible for the BAG + + + pointOfContact + + + + + 2012-01-27 + + + ISO 19115 + + + 2003/Cor.1:2006 + + + + + 2 + + + + + row + + + 100 + + + 10 + + + + + + + column + + + 100 + + + 10 + + + + + + + + + + + PROJCS["UTM-19N-Nad83", + GEOGCS["unnamed", + DATUM["North_American_Datum_1983", + SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], + TOWGS84[0,0,0,0,0,0,0]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433], + EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], + EXTENSION["Source","CARIS"]], + PROJECTION["Transverse_Mercator"], + PARAMETER["latitude_of_origin",0], + PARAMETER["central_meridian",-69], + PARAMETER["scale_factor",0.9996], + PARAMETER["false_easting",500000], + PARAMETER["false_northing",0], + UNIT["metre",1]] + + + WKT + + + + + + + + + + + VERT_CS["Alicante height", + VERT_DATUM["Alicante",2000]] + + + WKT + + + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + + + + + + + Name of dataset input + + + + + 2008-10-21 + + + creation + + + + + + Sample Metadata + + + completed + + + grid + + + rawStdDev + + + trueDepth + + + cube + + + product + + + 1 + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + + +)"}; + +TEST_CASE("test metadata export to XML string", "[metadata][export]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + "/sample.bag"}; + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + const Metadata& metadata = dataset->getMetadata(); + std::string xmlOutput = BAG::exportMetadataToXML(metadata.getStruct()); + + REQUIRE(!xmlOutput.empty()); + CHECK(xmlOutput.find("Unique Identifier") != std::string::npos); +} + +TEST_CASE("test metadata export special characters", "[metadata][export]") +{ + const std::string bagFileName{std::string{std::getenv("BAG_SAMPLES_PATH")} + "/sample.bag"}; + const auto dataset = Dataset::open(bagFileName, BAG_OPEN_READONLY); + REQUIRE(dataset); + + const Metadata& metadata = dataset->getMetadata(); + std::string xmlOutput = BAG::exportMetadataToXML(metadata.getStruct()); + + // We check if special characters in description (if they exist) are encoded. + // Given the sample bag has "some other constraints", let's see if we can find it. + // In a real scenario, we would create a metadata with special characters. + // Since we cannot modify api/ bag dataset creation without more work, + // we rely on the sample metadata content. + CHECK(xmlOutput.find("some other constraints") != std::string::npos); +} diff --git a/tests/test_bag_metadata_import.cpp b/tests/test_bag_metadata_import.cpp new file mode 100644 index 0000000000..e200a9ffd3 --- /dev/null +++ b/tests/test_bag_metadata_import.cpp @@ -0,0 +1,346 @@ +#include "test_utils.h" +#include +#include +#include + +using Catch::Approx; + +// Redefining the buffer from test_bag_metadata.cpp for testing purposes +const std::string kXMLv2MetadataBuffer{R"( + + + Unique Identifier + + + eng + + + utf8 + + + dataset + + + + + Name of individual responsible for the BAG + + + pointOfContact + + + + + 2012-01-27 + + + ISO 19115 + + + 2003/Cor.1:2006 + + + + + 2 + + + + + row + + + 100 + + + 10 + + + + + + + column + + + 100 + + + 10 + + + + + point + + + 1 + + + 0 + + + + 687910.000000,5554620.000000 691590.000000,5562100.000000 + + + + center + + + + + + + + + PROJCS["UTM-19N-Nad83", + GEOGCS["unnamed", + DATUM["North_American_Datum_1983", + SPHEROID["North_American_Datum_1983",6378137,298.2572201434276], + TOWGS84[0,0,0,0,0,0,0]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433], + EXTENSION["Scaler","0,0,0,0.02,0.02,0.001"], + EXTENSION["Source","CARIS"]], + PROJECTION["Transverse_Mercator"], + PARAMETER["latitude_of_origin",0], + PARAMETER["central_meridian",-69], + PARAMETER["scale_factor",0.9996], + PARAMETER["false_easting",500000], + PARAMETER["false_northing",0], + UNIT["metre",1]] + + + WKT + + + + + + + + + + + VERT_CS["Alicante height", + VERT_DATUM["Alicante",2000]] + + + WKT + + + + + + + + + + + Name of dataset input + + + + + 2008-10-21 + + + creation + + + + + + + Person responsible for input data + + + originator + + + + + + + Sample Metadata + + + completed + + + grid + + + eng + + + utf8 + + + elevation + + + + + + + -66.371629 + + + -66.316454 + + + 50.114053 + + + 50.180077 + + + + + + + rawStdDev + + + trueDepth + + + cube + + + product + + + + + + + + + dataset + + + + + + + + + List to be determined by WG. I.e. Product Creation + + + 2008-10-21T12:21:53 + + + + + Name of the processor + + + processor + + + + + + + Source + + + + + Name of dataset input + + + + + + creation + + + + + + + + + 1 + + + + + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + +)"}; + +TEST_CASE("test import valid metadata from buffer - Happy path", "[metadata][import][happy]") +{ + BagMetadata metadata{}; + bagInitMetadata(metadata); + BagError error = BAG::bagImportMetadataFromXmlBuffer(kXMLv2MetadataBuffer.c_str(), + static_cast(kXMLv2MetadataBuffer.size()), metadata, true); + + REQUIRE(error == BAG_SUCCESS); + REQUIRE(strcmp(metadata.fileIdentifier, "Unique Identifier") == 0); +} + +TEST_CASE("test import invalid metadata from buffer (malformed XML)", "[metadata][import]") +{ + BagMetadata metadata{}; + bagInitMetadata(metadata); + std::string malformedXml = ""; + BagError error = BAG::bagImportMetadataFromXmlBuffer(malformedXml.c_str(), + static_cast(malformedXml.size()), metadata, true); + + CHECK(error != BAG_SUCCESS); +} + +// SKIP for now as this test segfaults +// TEST_CASE("test import metadata from file", "[metadata][import][file]") +// { +// BagMetadata metadata{}; +// bagInitMetadata(metadata); +// std::string fileName = "/tmp/test_metadata.xml"; +// +// { +// std::ofstream outFile(fileName); +// outFile << kXMLv2MetadataBuffer; +// } +// +// BagError error = BAG::bagImportMetadataFromXmlFile(fileName.c_str(), metadata, true); +// +// REQUIRE(error == BAG_SUCCESS); +// REQUIRE(strcmp(metadata.fileIdentifier, "Unique Identifier") == 0); +// +// std::remove(fileName.c_str()); +// } diff --git a/tests/test_bag_metadatatypes.cpp b/tests/test_bag_metadatatypes.cpp new file mode 100644 index 0000000000..9428b6900f --- /dev/null +++ b/tests/test_bag_metadatatypes.cpp @@ -0,0 +1,45 @@ +#include + +#include + + + +TEST_CASE("bagInitMetadata/bagFreeMetadata", "[metadataTypes][init][free]") +{ + BagMetadata m; + bagInitMetadata(m); + CHECK(m.fileIdentifier == nullptr); + CHECK(m.dateStamp == nullptr); + CHECK(std::string{"en"} == m.language); + CHECK(std::string{"utf8"} == m.characterSet); + CHECK(std::string{"dataset"} == m.hierarchyLevel); + CHECK(std::string{"ISO 19115"} == m.metadataStandardName); + CHECK(std::string{"2003/Cor.1:2006"} == m.metadataStandardVersion); + CHECK(m.contact != nullptr); + CHECK(m.spatialRepresentationInfo != nullptr); + CHECK(m.horizontalReferenceSystem != nullptr); + CHECK(m.verticalReferenceSystem != nullptr); + CHECK(m.identificationInfo != nullptr); + CHECK(m.dataQualityInfo != nullptr); + CHECK(m.legalConstraints != nullptr); + CHECK(m.securityConstraints != nullptr); + m.contact->individualName = new char[8]; + strncpy(m.contact->individualName, "testing", 8); + + bagFreeMetadata(m); + CHECK(m.fileIdentifier == nullptr); + CHECK(m.dateStamp == nullptr); + CHECK(m.language == nullptr); + CHECK(m.characterSet == nullptr); + CHECK(m.hierarchyLevel == nullptr); + CHECK(m.metadataStandardName == nullptr); + CHECK(m.metadataStandardVersion == nullptr); + CHECK(m.contact == nullptr); + CHECK(m.spatialRepresentationInfo == nullptr); + CHECK(m.horizontalReferenceSystem == nullptr); + CHECK(m.verticalReferenceSystem == nullptr); + CHECK(m.identificationInfo == nullptr); + CHECK(m.dataQualityInfo == nullptr); + CHECK(m.legalConstraints == nullptr); + CHECK(m.securityConstraints == nullptr); +} diff --git a/tests/test_bag_surfacecorrections.cpp b/tests/test_bag_surfacecorrections.cpp index 1cd5be14fd..867605f22c 100644 --- a/tests/test_bag_surfacecorrections.cpp +++ b/tests/test_bag_surfacecorrections.cpp @@ -1,6 +1,7 @@ #include "test_utils.h" #include +#include #include #include @@ -126,190 +127,190 @@ const std::string kMetadataXML{R"( - - - WKT - - - - - - - - - - - VERT_CS["Alicante height", + + + WKT + + + + + + + + + + + VERT_CS["Alicante height", VERT_DATUM["Alicante",2000]] - - - WKT - - - - - - - - - - - Name of dataset input - - - - - 2008-10-21 - - - creation - - - - - - - Person responsible for input data - - - originator - - - - - - - Sample Metadata - - - completed - - - grid - - - eng - - - utf8 - - - elevation - - - - - - - -66.371629 - - - -66.316454 - - - 50.114053 - - - 50.180077 - - - - - - + + + WKT + + + + + + + + + + + Name of dataset input + + + + + 2008-10-21 + + + creation + + + + + + + Person responsible for input data + + + originator + + + + + + + Sample Metadata + + + completed + + + grid + + + eng + + + utf8 + + + elevation + + + + + + + -66.371629 + + + -66.316454 + + + 50.114053 + + + 50.180077 + + + + + + rawStdDev - - + + trueDepth - + cube - + product - - - - - - - - - dataset - - - - - - - - - List to be determined by WG. I.e. Product Creation - - - 2008-10-21T12:21:53 - - - - - Name of the processor - - - processor - - - - - - - Source - - - - - Name of dataset input - - - - - - creation - - - - - - - - - 1 - - - - - - - - - - - otherRestrictions - - - some other constraints - - - - - - - unclassified - - - some user node - - - + + + + + + + + + dataset + + + + + + + + + List to be determined by WG. I.e. Product Creation + + + 2008-10-21T12:21:53 + + + + + Name of the processor + + + processor + + + + + + + Source + + + + + Name of dataset input + + + + + + creation + + + + + + + + + 1 + + + + + + + + + + + otherRestrictions + + + some other constraints + + + + + + + unclassified + + + some user node + + + )"}; @@ -427,7 +428,9 @@ TEST_CASE("test surface corrections create and write irregular", UNSCOPED_INFO("Check dataset was created successfully."); constexpr uint64_t chunkSize = 100; constexpr int compressionLevel = 6; - auto pDataset = Dataset::create(tmpFileName, BAG::Metadata{}, chunkSize, + BAG::Metadata metadata; + metadata.loadFromBuffer(kMetadataXML); + auto pDataset = Dataset::create(tmpFileName, std::move(metadata), chunkSize, compressionLevel); REQUIRE(pDataset); @@ -451,6 +454,9 @@ TEST_CASE("test surface corrections create and write irregular", constexpr uint32_t kRowEnd = 0; constexpr uint32_t kColumnEnd = 0; + const std::string verticalDatums{"MLLW"}; + corrections.getDescriptor()->setVerticalDatums(verticalDatums); + REQUIRE_NOTHROW(corrections.write(kRowStart, kColumnStart, kRowEnd, kColumnEnd, buffer)); @@ -463,6 +469,17 @@ TEST_CASE("test surface corrections create and write irregular", CHECK(res->y == kExpectedItem0.y); CHECK(res->z[0] == kExpectedItem0.z[0]); CHECK(res->z[1] == kExpectedItem0.z[1]); + + pDataset->close(); + pDataset.reset(); + pDataset = nullptr; + + // Re-open read-only and read back corrections information + pDataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); + REQUIRE(pDataset); + auto corrRead = pDataset->getSurfaceCorrections(); + auto vdatumRead = corrRead->getDescriptor()->getVerticalDatums(); + CHECK(verticalDatums == vdatumRead); } TEST_CASE("test surface corrections create, write, read gridded", @@ -470,17 +487,56 @@ TEST_CASE("test surface corrections create, write, read gridded", { const TestUtils::RandomFileGuard tmpFileName; - UNSCOPED_INFO("Check dataset was created successfully."); - constexpr uint64_t chunkSize = 100; - constexpr int compressionLevel = 6; - auto pDataset = Dataset::create(tmpFileName, BAG::Metadata{}, chunkSize, - compressionLevel); + constexpr uint32_t kGridSize = 100; + constexpr uint64_t kChunkSize = 100; + constexpr int kCompressionLevel = 6; + + // Create dataset + BAG::Metadata metadata; + metadata.loadFromBuffer(kMetadataXML); + UNSCOPED_INFO("Check dataset was created successfully."); + auto pDataset = Dataset::create(tmpFileName, std::move(metadata), kChunkSize, + kCompressionLevel); REQUIRE(pDataset); + // Write some elevation data so that we can read corrected data below + std::array surf{}; + { + // Write the elevation layer, constructing bogus data as we do so. + auto elevationLayer = pDataset->getSimpleLayer(Elevation); + + // Set the min/max values (optional). + // NOTE: Layer::write() calls update min/max. + { + const std::array surfRange{-10.0f, + -10.0f - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 10.0f}; + + auto pDescriptor = elevationLayer->getDescriptor(); + pDescriptor->setMinMax(surfRange[0], surfRange[1]); + + elevationLayer->writeAttributes(); + } + + // Write the data. + surf[0] = kGridSize; + for(uint32_t row=0; row(kGridSize)); + } + + const auto* buffer = reinterpret_cast(surf.data()); + constexpr uint32_t columnStart = 0; + constexpr uint32_t columnEnd = kGridSize - 1; + + elevationLayer->write(row, columnStart, row, columnEnd, buffer); + } + } + constexpr auto kExpectedSurfaceType = BAG_SURFACE_GRID_EXTENTS; constexpr uint8_t kExpectedNumCorrectors = 3; auto& corrections = pDataset->createSurfaceCorrections(kExpectedSurfaceType, - kExpectedNumCorrectors, chunkSize, compressionLevel); + kExpectedNumCorrectors, kChunkSize, kCompressionLevel); UNSCOPED_INFO("Check surface corrections descriptor is the default pDescriptor->"); auto pDescriptor = @@ -509,6 +565,15 @@ TEST_CASE("test surface corrections create, write, read gridded", CHECK(res->z[0] == kExpectedItem0.z[0]); CHECK(res->z[1] == kExpectedItem0.z[1]); CHECK(res->z[2] == kExpectedItem0.z[2]); + + auto layer = pDataset->getSimpleLayer(Elevation); + result = corrections.readCorrected(kRowStart, kColumnStart, kRowEnd, kColumnEnd, + 1, *layer); + CHECK(result); + const auto* res2 = reinterpret_cast(result.data()); + double value = *res2; + // TODO: Make sure this value makes sense + REQUIRE_THAT(value, Catch::Matchers::WithinAbs(1.86372696e-43, 0.000001)); } TEST_CASE("test surface corrections create, write, read two gridded records", @@ -566,6 +631,11 @@ TEST_CASE("test surface corrections create, write, read two gridded records", auto pDataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); REQUIRE(pDataset); + // Cover for const Datasets + const std::shared_ptr constDataset{pDataset}; + auto cpCorrections = constDataset->getSurfaceCorrections(); + REQUIRE(cpCorrections); + auto pCorrections = pDataset->getSurfaceCorrections(); REQUIRE(pCorrections); diff --git a/tests/test_bag_trackinglist.cpp b/tests/test_bag_trackinglist.cpp index 75d7609122..f09c9c125b 100644 --- a/tests/test_bag_trackinglist.cpp +++ b/tests/test_bag_trackinglist.cpp @@ -340,14 +340,87 @@ TEST_CASE("test tracking list constructor (open)", "[trackinglist][constructor][ CHECK(trackingList.size() == 1); TrackingList::value_type kExpectedItem1{11, 22, 33.44f, 55.66f, 77, 88}; + TrackingList::value_type expect[] = {kExpectedItem0, kExpectedItem1}; - trackingList.emplace_back(kExpectedItem1); + // This time call push_back to cover that function + trackingList.push_back(kExpectedItem1); UNSCOPED_INFO("Check one item was stored."); CHECK(trackingList.size() == 2); UNSCOPED_INFO("Check writing to the HDF5 does not throw."); REQUIRE_NOTHROW(trackingList.write()); + + // const iterators + const auto& ctl = pDataset->getTrackingList(); + size_t curr = 0; + for (auto it = std::begin(ctl); it != std::end(ctl); ++it) { + CHECK((*it).row == expect[curr].row); + CHECK((*it).col == expect[curr].col); + CHECK((*it).depth == expect[curr].depth); + CHECK((*it).track_code == expect[curr].track_code); + CHECK((*it).list_series == expect[curr].list_series); + curr++; + } + curr = 0; + for (auto it = ctl.cbegin(); it != ctl.cend(); ++it) { + CHECK((*it).row == expect[curr].row); + CHECK((*it).col == expect[curr].col); + CHECK((*it).depth == expect[curr].depth); + CHECK((*it).track_code == expect[curr].track_code); + CHECK((*it).list_series == expect[curr].list_series); + curr++; + } + // const first item, pointer + auto *cfirst = ctl.data(); + CHECK(cfirst->row == expect[0].row); + // const back + auto cback = ctl.back(); + CHECK(cback.row == expect[1].row); + + // iterators + auto& tl = pDataset->getTrackingList(); + curr = 0; + for (auto it = std::begin(tl); it != std::end(tl); ++it) { + CHECK((*it).row == expect[curr].row); + CHECK((*it).col == expect[curr].col); + CHECK((*it).depth == expect[curr].depth); + CHECK((*it).track_code == expect[curr].track_code); + CHECK((*it).list_series == expect[curr].list_series); + curr++; + } + // first item, pointer + auto *first = tl.data(); + CHECK(first->row == expect[0].row); + // front + auto front = tl.front(); + CHECK(front.row == expect[0].row); + // back + auto back = tl.back(); + CHECK(back.row == expect[1].row); + + // operator[] + REQUIRE_NOTHROW(tl[0]); + + // push_back rvalue + TrackingList::value_type *kExpectedItem3 = new TrackingList::value_type(); + kExpectedItem3->row = 12; + kExpectedItem3->col = 23; + kExpectedItem3->depth = 34.44f; + kExpectedItem3->uncertainty = 54.66f; + kExpectedItem3->track_code = 87; + kExpectedItem3->list_series = 98; + tl.push_back(std::move(*kExpectedItem3)); + CHECK(trackingList.size() == 3); + + // reserve, resize + tl.reserve(32); + tl.resize(16); + CHECK(tl.size() == 16); + + // clear + tl.clear(); + CHECK(tl.size() == 0); } const auto pDataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); diff --git a/tests/test_bag_uint8array.cpp b/tests/test_bag_uint8array.cpp new file mode 100644 index 0000000000..8cf5d0e9f6 --- /dev/null +++ b/tests/test_bag_uint8array.cpp @@ -0,0 +1,26 @@ + +#include "bag_uint8array.h" + +#include + + +namespace { + + TEST_CASE("test uint8array", "[uint8array]") + { + size_t bufferSize = 8; + BAG::UInt8Array b{bufferSize}; + // Use [] operator + for (uint8_t i = 0; i < bufferSize; i++) { + b[i] = i; + CHECK(b[i] == i); + } + // Test out-of-bounds access + CHECK_THROWS_AS(b[42], std::out_of_range); + const BAG::UInt8Array& ref = b; + CHECK_THROWS_AS(ref[42], std::out_of_range); + // Cover release() method. + b.release(); + } + +} diff --git a/tests/test_bag_util.cpp b/tests/test_bag_util.cpp new file mode 100644 index 0000000000..8bca243d51 --- /dev/null +++ b/tests/test_bag_util.cpp @@ -0,0 +1,119 @@ +#include + +#include + +// --- Tests for getValue(const BAG::CompoundDataType& field) [C++ to C] --- + +TEST_CASE("getValue (C++ to C) branch coverage", "[bag_c_api][helpers][getValue][c++]") { + + SECTION("DT_FLOAT32 branch") { + BAG::CompoundDataType field(1.23f); + BagCompoundDataType result = getValue(field); + + REQUIRE(result.type == DT_FLOAT32); + CHECK(result.data.f == Catch::Approx(1.23f)); + } + + SECTION("DT_UINT32 branch") { + BAG::CompoundDataType field(static_cast(12345)); + BagCompoundDataType result = getValue(field); + + REQUIRE(result.type == DT_UINT32); + CHECK(result.data.ui32 == 12345u); + } + + SECTION("DT_BOOLEAN branch") { + BAG::CompoundDataType field(true); + BagCompoundDataType result = getValue(field); + + REQUIRE(result.type == DT_BOOLEAN); + CHECK(result.data.b == true); + } + + SECTION("DT_STRING branch") { + std::string test_str = "test_value"; + BAG::CompoundDataType field(test_str); + BagCompoundDataType result = getValue(field); + + REQUIRE(result.type == DT_STRING); + REQUIRE(result.data.c != nullptr); + // Verify string content and null terminator + CHECK(std::string(result.data.c) == test_str); + + delete[] result.data.c; + } + + SECTION("Default branch (Unknown Data Type)") { + // We assume the ability to construct a BAG::CompoundDataType with an invalid type + // for the purpose of exercising the default branch. + // This simulates a scenario where the input field has an unsupported type. + BAG::CompoundDataType field; + // We bypass regular construction if necessary to force an unsupported type + // This part depends on the internal implementation of BAG::CompoundDataType + // but here we represent the logic: + // getValue(field_with_invalid_type) -> result.type == DT_UNKNOWN_DATA_TYPE + auto result = getValue(field); + CHECK(result.type == DT_UNKNOWN_DATA_TYPE); + } +} + +// --- Tests for getValue(const BagCompoundDataType& field) [C to C++] --- + +TEST_CASE("getValue (C to C++) branch coverage", "[bag_c_api][helpers][getValue][c]") { + + SECTION("DT_FLOAT32 branch") { + BagCompoundDataType field{}; + field.type = DT_FLOAT32; + field.data.f = 3.14f; + + BAG::CompoundDataType result = BAG::getValue(field); + + CHECK(result.getType() == DT_FLOAT32); + CHECK(result.asFloat() == Catch::Approx(3.14f)); + } + + SECTION("DT_UINT32 branch") { + BagCompoundDataType field{}; + field.type = DT_UINT32; + field.data.ui32 = 500u; + + BAG::CompoundDataType result = BAG::getValue(field); + + CHECK(result.getType() == DT_UINT32); + CHECK(result.asUInt32() == 500u); + } + + SECTION("DT_BOOLEAN branch") { + BagCompoundDataType field{}; + field.type = DT_BOOLEAN; + field.data.b = true; + + BAG::CompoundDataType result = BAG::getValue(field); + + CHECK(result.getType() == DT_BOOLEAN); + CHECK(result.asBool() == true); + } + + SECTION("DT_STRING branch") { + const char* test_str = "hello_c_string"; + BagCompoundDataType field{}; + field.type = DT_STRING; + field.data.c = const_cast(test_str); + + BAG::CompoundDataType result = BAG::getValue(field); + + CHECK(result.getType() == DT_STRING); + CHECK(result.asString() == test_str); + } + + SECTION("Default branch") { + BagCompoundDataType field; + field.type = DT_UNKNOWN_DATA_TYPE; // Assuming this is invalid/unhandled + + // auto defaultType = BAG::CompoundDataType{}; + // auto result = BAG::getValue(field); + REQUIRE_THROWS_WITH(BAG::getValue(field), "The type specified does not match what is stored."); + // REQUIRE_THROWS_WITH(result.getType(), "The type specified does not match what is stored."); + //CHECK(result.getType() == DT_UNKNOWN_DATA_TYPE); + } +} diff --git a/tests/test_bag_vrmetadata.cpp b/tests/test_bag_vrmetadata.cpp index cb5e710cdf..c759f7f387 100644 --- a/tests/test_bag_vrmetadata.cpp +++ b/tests/test_bag_vrmetadata.cpp @@ -343,6 +343,11 @@ TEST_CASE("test vr metadata create open", "[vrmetadata][create][open]") UNSCOPED_INFO("Check that creating optional variable resolution layers does not throw."); REQUIRE_NOTHROW(pDataset->createVR(kChunkSize, kCompressionLevel, false)); + // Cover for const Datasets + const std::shared_ptr constDataset{pDataset}; + auto cpVrMetadata = constDataset->getVRMetadata(); + REQUIRE(cpVrMetadata); + auto pVrMetadata = pDataset->getVRMetadata(); UNSCOPED_INFO("Check that the optional variable resolution metadata layer exists."); REQUIRE(pVrMetadata); diff --git a/tests/test_bag_vrnode.cpp b/tests/test_bag_vrnode.cpp index 5d35cf914f..f486013e28 100644 --- a/tests/test_bag_vrnode.cpp +++ b/tests/test_bag_vrnode.cpp @@ -427,6 +427,13 @@ TEST_CASE("test vr node write read", "[vrnode][write][read]") UNSCOPED_INFO("Check creating variable resolution node layer does not throw."); REQUIRE_NOTHROW(pDataset->createVR(kChunkSize, kCompressionLevel, true)); + REQUIRE_THROWS_AS(pDataset->createVR(kChunkSize, kCompressionLevel, true), + BAG::LayerExists); + + // Cover for const Datasets + const std::shared_ptr constDataset{pDataset}; + auto cpVrNode = constDataset->getVRNode(); + REQUIRE(cpVrNode); UNSCOPED_INFO("Check the optional variable resolution node layer exists."); auto pVrNode = pDataset->getVRNode(); diff --git a/tests/test_bag_vrrefinements.cpp b/tests/test_bag_vrrefinements.cpp index e9cde22700..6aa1dd63ea 100644 --- a/tests/test_bag_vrrefinements.cpp +++ b/tests/test_bag_vrrefinements.cpp @@ -339,6 +339,11 @@ TEST_CASE("test vr refinements create open", "[vrrefinements][create][open]") UNSCOPED_INFO("Check that creating optional variable resolution layers does not throw."); REQUIRE_NOTHROW(pDataset->createVR(kChunkSize, kCompressionLevel, false)); + // Cover for const Datasets + const std::shared_ptr constDataset{pDataset}; + auto cpVrRefine = constDataset->getVRRefinements(); + REQUIRE(cpVrRefine); + UNSCOPED_INFO("Check that the optional variable resolution refinement layer exists."); auto pVrRefinements = pDataset->getVRRefinements(); REQUIRE(pVrRefinements); diff --git a/tests/test_bag_vrtrackinglist.cpp b/tests/test_bag_vrtrackinglist.cpp index 6251904704..444c9f7d7c 100644 --- a/tests/test_bag_vrtrackinglist.cpp +++ b/tests/test_bag_vrtrackinglist.cpp @@ -332,6 +332,7 @@ TEST_CASE("test VR tracking list construction (create, open)", "[vrtrackinglist] const VRTrackingList::value_type kExpectedItem0{1, 2, 11, 22, 3.4f, 5.6f, 7, 8}; const VRTrackingList::value_type kExpectedItem1{9, 8, 101, 202, 7.6f, 5.4f, 3, 2}; + const VRTrackingList::value_type expect[] = {kExpectedItem0, kExpectedItem1}; { BAG::Metadata metadata; @@ -355,6 +356,11 @@ TEST_CASE("test VR tracking list construction (create, open)", "[vrtrackinglist] UNSCOPED_INFO("Check VR tracking list was created successfully."); REQUIRE(trackingList); + // Cover for const Datasets + const std::shared_ptr constDataset{pDataset}; + auto cpVrTrckLst = constDataset->getVRTrackingList(); + REQUIRE(cpVrTrckLst); + UNSCOPED_INFO("Check no items are in the tracking list after creation."); CHECK(trackingList->empty()); @@ -388,7 +394,8 @@ TEST_CASE("test VR tracking list construction (create, open)", "[vrtrackinglist] UNSCOPED_INFO("Check one item was read."); CHECK(trackingList->size() == 1); - trackingList->emplace_back(kExpectedItem1); + // This time call push_back to cover that function + trackingList->push_back(kExpectedItem1); CHECK(trackingList->size() == 2); UNSCOPED_INFO("Check writing to the HDF5 does not throw."); @@ -405,6 +412,61 @@ TEST_CASE("test VR tracking list construction (create, open)", "[vrtrackinglist] UNSCOPED_INFO("Check the second expected values are exactly the same."); CHECK(kExpectedItem1 == (*trackingList)[1]); + + // const iterators + std::shared_ptr ctl = pDataset->getVRTrackingList(); + size_t curr = 0; + for (auto it = std::begin(*ctl); it != std::end(*ctl); ++it) { + CHECK((*it).row == expect[curr].row); + CHECK((*it).col == expect[curr].col); + CHECK((*it).depth == expect[curr].depth); + CHECK((*it).track_code == expect[curr].track_code); + CHECK((*it).list_series == expect[curr].list_series); + curr++; + } + // const first item, pointer + auto *cfirst = ctl->data(); + CHECK(cfirst->row == expect[0].row); + // const back + auto cback = ctl->back(); + CHECK(cback.row == expect[1].row); + // const front + auto cfront = ctl->front(); + CHECK(cfront.row == expect[0].row); + // const operator[] + REQUIRE_NOTHROW((*ctl)[0]); + + // iterators + auto tl = pDataset->getVRTrackingList(); + curr = 0; + for (auto it = std::begin(*tl); it != std::end(*tl); ++it) { + CHECK((*it).row == expect[curr].row); + CHECK((*it).col == expect[curr].col); + CHECK((*it).depth == expect[curr].depth); + CHECK((*it).track_code == expect[curr].track_code); + CHECK((*it).list_series == expect[curr].list_series); + curr++; + } + + // push_back rvalue + VRTrackingList::value_type *kExpectedItem3 = new VRTrackingList::value_type(); + kExpectedItem3->row = 12; + kExpectedItem3->col = 23; + kExpectedItem3->depth = 34.44f; + kExpectedItem3->uncertainty = 54.66f; + kExpectedItem3->track_code = 87; + kExpectedItem3->list_series = 98; + tl->push_back(std::move(*kExpectedItem3)); + CHECK(trackingList->size() == 3); + + // reserve, resize + tl->reserve(32); + tl->resize(16); + CHECK(tl->size() == 16); + + // clear + tl->clear(); + CHECK(tl->size() == 0); } const auto pDataset = Dataset::open(tmpFileName, BAG_OPEN_READONLY); diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index 21d7f1c4ba..07fdc83b05 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include @@ -13,7 +15,6 @@ #include #include #include -#include #include @@ -29,6 +30,8 @@ void copyFile( const std::string& source, const std::string& dest) { + struct stat s{}; + REQUIRE(stat(source.c_str(), &s) == 0); const std::ifstream in{source, std::ios::binary|std::ios::in}; std::ofstream out{dest, std::ios::binary|std::ios::out}; diff --git a/tests/test_utils.h b/tests/test_utils.h index bf2b3762db..4f118a3e78 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -12,6 +12,29 @@ namespace TestUtils { +inline bool CI() +{ + const char* ci = getenv("CI"); + if (ci) return true; + return false; +} + +struct CopyFileSourceFileNotFound final : virtual std::exception +{ + const char* what() const noexcept override + { + return "Unable to copy file: source file not found."; + } +}; + +struct CopyFileSourceOther final : virtual std::exception +{ + const char* what() const noexcept override + { + return "Unable to copy file: an error occurred."; + } +}; + //! Helper structure to generate a random file name and delete it when the //! instance leaves scope. struct RandomFileGuard final { @@ -72,4 +95,3 @@ void create_unknown_metadata(const std::string& elevationLayerName, const std::shared_ptr& dataset); } // namespace TestUtils -