From ef8b9998d61b2db3e358ea6935304afc26d79205 Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Sat, 17 Jun 2023 13:54:56 -0700 Subject: [PATCH] HDF5: Fix Char Type Matching (#1433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HDF5: Fix Char Type Matching In HDF5, there are only the signed and unsigned char type. The third `char` type is an alias to one or the other, different to the C/C++ fundamental types. This tries to fix the type matching order to be platform independent between ppc64le/aarch64/x86-64. * Add failing HDF5 test * loadChunk(): consider equivalent char types for casting * Doc strings * newline & comment * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Franz Pöschel Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- include/openPMD/Datatype.hpp | 74 ++++++ include/openPMD/Datatype.tpp | 356 ++++++++++++++++++++++++++++ include/openPMD/DatatypeHelpers.hpp | 281 +--------------------- include/openPMD/RecordComponent.tpp | 3 +- src/IO/HDF5/HDF5Auxiliary.cpp | 7 +- src/IO/HDF5/HDF5IOHandler.cpp | 7 +- test/SerialIOTest.cpp | 50 ++++ 7 files changed, 495 insertions(+), 283 deletions(-) create mode 100644 include/openPMD/Datatype.tpp diff --git a/include/openPMD/Datatype.hpp b/include/openPMD/Datatype.hpp index f9661b60e8..24e60c1d22 100644 --- a/include/openPMD/Datatype.hpp +++ b/include/openPMD/Datatype.hpp @@ -651,6 +651,41 @@ inline bool isSameInteger(Datatype d) return false; } +/** + * Determines if d represents a char type. + * + * @param d An openPMD datatype. + * @return true If d is a scalar char, signed char or unsigned char. + * @return false Otherwise. + */ +constexpr bool isChar(Datatype d) +{ + switch (d) + { + case Datatype::CHAR: + case Datatype::SCHAR: + case Datatype::UCHAR: + return true; + default: + return false; + } +} + +/** + * Determines if d and T_Char are char types of same representation. + * + * Same representation means that on platforms with signed `char` type, `char` + * and `signed char` are considered to be eqivalent, similarly on platforms + * with unsigned `char` type. + * + * @tparam T_Char A type, as template parameter. + * @param d A type, as openPMD datatype. + * @return true If both types are chars of the same representation. + * @return false Otherwise. + */ +template +constexpr bool isSameChar(Datatype d); + /** Comparison for two Datatypes * * Besides returning true for the same types, identical implementations on @@ -710,6 +745,43 @@ void warnWrongDtype(std::string const &key, Datatype store, Datatype request); std::ostream &operator<<(std::ostream &, openPMD::Datatype const &); +/** + * Generalizes switching over an openPMD datatype. + * + * Will call the function template found at Action::call< T >(), instantiating T + * with the C++ internal datatype corresponding to the openPMD datatype. + * + * @tparam ReturnType The function template's return type. + * @tparam Action The struct containing the function template. + * @tparam Args The function template's argument types. + * @param dt The openPMD datatype. + * @param args The function template's arguments. + * @return Passes on the result of invoking the function template with the given + * arguments and with the template parameter specified by dt. + */ +template +constexpr auto switchType(Datatype dt, Args &&...args) + -> decltype(Action::template call(std::forward(args)...)); + +/** + * Generalizes switching over an openPMD datatype. + * + * Will call the function template found at Action::call< T >(), instantiating T + * with the C++ internal datatype corresponding to the openPMD datatype. + * Ignores vector and array types. + * + * @tparam ReturnType The function template's return type. + * @tparam Action The struct containing the function template. + * @tparam Args The function template's argument types. + * @param dt The openPMD datatype. + * @param args The function template's arguments. + * @return Passes on the result of invoking the function template with the given + * arguments and with the template parameter specified by dt. + */ +template +constexpr auto switchNonVectorType(Datatype dt, Args &&...args) + -> decltype(Action::template call(std::forward(args)...)); + } // namespace openPMD #if !defined(_MSC_VER) @@ -737,3 +809,5 @@ inline bool operator!=(openPMD::Datatype d, openPMD::Datatype e) /** @} */ #endif + +#include "openPMD/Datatype.tpp" diff --git a/include/openPMD/Datatype.tpp b/include/openPMD/Datatype.tpp new file mode 100644 index 0000000000..64d6d30d5b --- /dev/null +++ b/include/openPMD/Datatype.tpp @@ -0,0 +1,356 @@ +/* Copyright 2017-2023 Fabian Koller, Franz Poeschel, Axel Huebl + * + * This file is part of openPMD-api. + * + * openPMD-api is free software: you can redistribute it and/or modify + * it under the terms of of either the GNU General Public License or + * the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * openPMD-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License and the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * and the GNU Lesser General Public License along with openPMD-api. + * If not, see . + */ +#pragma once + +#include "openPMD/Datatype.hpp" + +#include +#include // std::void_t +#include // std::forward + +namespace openPMD +{ +namespace detail +{ + // std::void_t is C++17 + template + using void_t = void; + + /* + * Check whether class T has a member "errorMsg" convertible + * to type std::string. + * Used to give helpful compile-time error messages with static_assert + * down in CallUndefinedDatatype. + */ + template + struct HasErrorMessageMember + { + static constexpr bool value = false; + }; + + template + struct HasErrorMessageMember> + { + static constexpr bool value = true; + }; + + /** + * Purpose of this struct is to detect at compile time whether + * Action::template operator()\<0\>() exists. If yes, call + * Action::template operator()\() with the passed arguments. + * If not, throw an error. + * + * @tparam n As in switchType(). + * @tparam ReturnType As in switchType(). + * @tparam Action As in switchType(). + * @tparam Args As in switchType(). + */ + template + struct CallUndefinedDatatype + { + static ReturnType call(Args &&...args) + { + if constexpr (HasErrorMessageMember::value) + { + throw std::runtime_error( + "[" + std::string(Action::errorMsg) + + "] Unknown Datatype."); + } + else + { + return Action::template call(std::forward(args)...); + } + throw std::runtime_error("Unreachable!"); + } + }; +} // namespace detail + +/** + * Generalizes switching over an openPMD datatype. + * + * Will call the function template found at Action::call< T >(), instantiating T + * with the C++ internal datatype corresponding to the openPMD datatype. + * + * @tparam ReturnType The function template's return type. + * @tparam Action The struct containing the function template. + * @tparam Args The function template's argument types. + * @param dt The openPMD datatype. + * @param args The function template's arguments. + * @return Passes on the result of invoking the function template with the given + * arguments and with the template parameter specified by dt. + */ +template +constexpr auto switchType(Datatype dt, Args &&...args) + -> decltype(Action::template call(std::forward(args)...)) +{ + using ReturnType = + decltype(Action::template call(std::forward(args)...)); + switch (dt) + { + case Datatype::CHAR: + return Action::template call(std::forward(args)...); + case Datatype::UCHAR: + return Action::template call( + std::forward(args)...); + case Datatype::SCHAR: + return Action::template call(std::forward(args)...); + case Datatype::SHORT: + return Action::template call(std::forward(args)...); + case Datatype::INT: + return Action::template call(std::forward(args)...); + case Datatype::LONG: + return Action::template call(std::forward(args)...); + case Datatype::LONGLONG: + return Action::template call(std::forward(args)...); + case Datatype::USHORT: + return Action::template call( + std::forward(args)...); + case Datatype::UINT: + return Action::template call(std::forward(args)...); + case Datatype::ULONG: + return Action::template call( + std::forward(args)...); + case Datatype::ULONGLONG: + return Action::template call( + std::forward(args)...); + case Datatype::FLOAT: + return Action::template call(std::forward(args)...); + case Datatype::DOUBLE: + return Action::template call(std::forward(args)...); + case Datatype::LONG_DOUBLE: + return Action::template call(std::forward(args)...); + case Datatype::CFLOAT: + return Action::template call>( + std::forward(args)...); + case Datatype::CDOUBLE: + return Action::template call>( + std::forward(args)...); + case Datatype::CLONG_DOUBLE: + return Action::template call>( + std::forward(args)...); + case Datatype::STRING: + return Action::template call(std::forward(args)...); + case Datatype::VEC_CHAR: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_SHORT: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_INT: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_LONG: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_LONGLONG: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_UCHAR: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_SCHAR: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_USHORT: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_UINT: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_ULONG: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_ULONGLONG: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_FLOAT: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_DOUBLE: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_LONG_DOUBLE: + return Action::template call>( + std::forward(args)...); + case Datatype::VEC_CFLOAT: + return Action::template call>>( + std::forward(args)...); + case Datatype::VEC_CDOUBLE: + return Action::template call>>( + std::forward(args)...); + case Datatype::VEC_CLONG_DOUBLE: + return Action::template call>>( + std::forward(args)...); + case Datatype::VEC_STRING: + return Action::template call>( + std::forward(args)...); + case Datatype::ARR_DBL_7: + return Action::template call>( + std::forward(args)...); + case Datatype::BOOL: + return Action::template call(std::forward(args)...); + case Datatype::UNDEFINED: + return detail:: + CallUndefinedDatatype<0, ReturnType, Action, Args &&...>::call( + std::forward(args)...); + default: + throw std::runtime_error( + "Internal error: Encountered unknown datatype (switchType) ->" + + std::to_string(static_cast(dt))); + } +} + +/** + * Generalizes switching over an openPMD datatype. + * + * Will call the function template found at Action::call< T >(), instantiating T + * with the C++ internal datatype corresponding to the openPMD datatype. + * Ignores vector and array types. + * + * @tparam ReturnType The function template's return type. + * @tparam Action The struct containing the function template. + * @tparam Args The function template's argument types. + * @param dt The openPMD datatype. + * @param args The function template's arguments. + * @return Passes on the result of invoking the function template with the given + * arguments and with the template parameter specified by dt. + */ +template +constexpr auto switchNonVectorType(Datatype dt, Args &&...args) + -> decltype(Action::template call(std::forward(args)...)) +{ + using ReturnType = + decltype(Action::template call(std::forward(args)...)); + switch (dt) + { + case Datatype::CHAR: + return Action::template call(std::forward(args)...); + case Datatype::UCHAR: + return Action::template call( + std::forward(args)...); + case Datatype::SCHAR: + return Action::template call(std::forward(args)...); + case Datatype::SHORT: + return Action::template call(std::forward(args)...); + case Datatype::INT: + return Action::template call(std::forward(args)...); + case Datatype::LONG: + return Action::template call(std::forward(args)...); + case Datatype::LONGLONG: + return Action::template call(std::forward(args)...); + case Datatype::USHORT: + return Action::template call( + std::forward(args)...); + case Datatype::UINT: + return Action::template call(std::forward(args)...); + case Datatype::ULONG: + return Action::template call( + std::forward(args)...); + case Datatype::ULONGLONG: + return Action::template call( + std::forward(args)...); + case Datatype::FLOAT: + return Action::template call(std::forward(args)...); + case Datatype::DOUBLE: + return Action::template call(std::forward(args)...); + case Datatype::LONG_DOUBLE: + return Action::template call(std::forward(args)...); + case Datatype::CFLOAT: + return Action::template call>( + std::forward(args)...); + case Datatype::CDOUBLE: + return Action::template call>( + std::forward(args)...); + case Datatype::CLONG_DOUBLE: + return Action::template call>( + std::forward(args)...); + case Datatype::STRING: + return Action::template call(std::forward(args)...); + case Datatype::BOOL: + return Action::template call(std::forward(args)...); + case Datatype::UNDEFINED: + return detail:: + CallUndefinedDatatype<0, ReturnType, Action, Args &&...>::call( + std::forward(args)...); + default: + throw std::runtime_error( + "Internal error: Encountered unknown datatype (switchType) ->" + + std::to_string(static_cast(dt))); + } +} + +namespace detail +{ + template + struct is_char + { + static constexpr bool value = false; + }; + template <> + struct is_char + { + static constexpr bool value = true; + }; + template <> + struct is_char + { + static constexpr bool value = true; + }; + template <> + struct is_char + { + static constexpr bool value = true; + }; + template + constexpr bool is_char_v = is_char::value; + + template + inline bool isSameChar() + { + return + // both must be char types + is_char_v && is_char_v && + // both must have equivalent sign + std::is_signed_v == std::is_signed_v && + // both must have equivalent size + sizeof(T_Char1) == sizeof(T_Char2); + } + + template + struct IsSameChar + { + template + static bool call() + { + return isSameChar(); + } + + static constexpr char const *errorMsg = "IsSameChar"; + }; + +} // namespace detail + +template +constexpr inline bool isSameChar(Datatype d) +{ + return switchType>(d); +} +} // namespace openPMD diff --git a/include/openPMD/DatatypeHelpers.hpp b/include/openPMD/DatatypeHelpers.hpp index 07f3c7fc37..b2e9171754 100644 --- a/include/openPMD/DatatypeHelpers.hpp +++ b/include/openPMD/DatatypeHelpers.hpp @@ -1,4 +1,4 @@ -/* Copyright 2017-2021 Fabian Koller, Franz Poeschel, Axel Huebl +/* Copyright 2023 Franz Poeschel * * This file is part of openPMD-api. * @@ -18,282 +18,11 @@ * and the GNU Lesser General Public License along with openPMD-api. * If not, see . */ -#pragma once - -#include "openPMD/Datatype.hpp" - -#include -#include -#include // std::forward - -namespace openPMD -{ -namespace detail -{ - // std::void_t is C++17 - template - using void_t = void; - - /* - * Check whether class T has a member "errorMsg" convertible - * to type std::string. - * Used to give helpful compile-time error messages with static_assert - * down in CallUndefinedDatatype. - */ - template - struct HasErrorMessageMember - { - static constexpr bool value = false; - }; - - template - struct HasErrorMessageMember > - { - static constexpr bool value = true; - }; - /** - * Purpose of this struct is to detect at compile time whether - * Action::template operator()\<0\>() exists. If yes, call - * Action::template operator()\() with the passed arguments. - * If not, throw an error. - * - * @tparam n As in switchType(). - * @tparam ReturnType As in switchType(). - * @tparam Action As in switchType(). - * @tparam Args As in switchType(). - */ - template - struct CallUndefinedDatatype - { - static ReturnType call(Args &&...args) - { - if constexpr (HasErrorMessageMember::value) - { - throw std::runtime_error( - "[" + std::string(Action::errorMsg) + - "] Unknown Datatype."); - } - else - { - return Action::template call(std::forward(args)...); - } - throw std::runtime_error("Unreachable!"); - } - }; -} // namespace detail +#pragma once -/** - * Generalizes switching over an openPMD datatype. - * - * Will call the function template found at Action::call< T >(), instantiating T - * with the C++ internal datatype corresponding to the openPMD datatype. - * - * @tparam ReturnType The function template's return type. - * @tparam Action The struct containing the function template. - * @tparam Args The function template's argument types. - * @param dt The openPMD datatype. - * @param args The function template's arguments. - * @return Passes on the result of invoking the function template with the given - * arguments and with the template parameter specified by dt. +/* + * Legacy header, its functions are now included directly in Datatype.hpp. */ -template -auto switchType(Datatype dt, Args &&...args) - -> decltype(Action::template call(std::forward(args)...)) -{ - using ReturnType = - decltype(Action::template call(std::forward(args)...)); - switch (dt) - { - case Datatype::CHAR: - return Action::template call(std::forward(args)...); - case Datatype::UCHAR: - return Action::template call( - std::forward(args)...); - case Datatype::SCHAR: - return Action::template call(std::forward(args)...); - case Datatype::SHORT: - return Action::template call(std::forward(args)...); - case Datatype::INT: - return Action::template call(std::forward(args)...); - case Datatype::LONG: - return Action::template call(std::forward(args)...); - case Datatype::LONGLONG: - return Action::template call(std::forward(args)...); - case Datatype::USHORT: - return Action::template call( - std::forward(args)...); - case Datatype::UINT: - return Action::template call(std::forward(args)...); - case Datatype::ULONG: - return Action::template call( - std::forward(args)...); - case Datatype::ULONGLONG: - return Action::template call( - std::forward(args)...); - case Datatype::FLOAT: - return Action::template call(std::forward(args)...); - case Datatype::DOUBLE: - return Action::template call(std::forward(args)...); - case Datatype::LONG_DOUBLE: - return Action::template call(std::forward(args)...); - case Datatype::CFLOAT: - return Action::template call >( - std::forward(args)...); - case Datatype::CDOUBLE: - return Action::template call >( - std::forward(args)...); - case Datatype::CLONG_DOUBLE: - return Action::template call >( - std::forward(args)...); - case Datatype::STRING: - return Action::template call(std::forward(args)...); - case Datatype::VEC_CHAR: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_SHORT: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_INT: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_LONG: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_LONGLONG: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_UCHAR: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_SCHAR: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_USHORT: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_UINT: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_ULONG: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_ULONGLONG: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_FLOAT: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_DOUBLE: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_LONG_DOUBLE: - return Action::template call >( - std::forward(args)...); - case Datatype::VEC_CFLOAT: - return Action::template call > >( - std::forward(args)...); - case Datatype::VEC_CDOUBLE: - return Action::template call > >( - std::forward(args)...); - case Datatype::VEC_CLONG_DOUBLE: - return Action::template call > >( - std::forward(args)...); - case Datatype::VEC_STRING: - return Action::template call >( - std::forward(args)...); - case Datatype::ARR_DBL_7: - return Action::template call >( - std::forward(args)...); - case Datatype::BOOL: - return Action::template call(std::forward(args)...); - case Datatype::UNDEFINED: - return detail:: - CallUndefinedDatatype<0, ReturnType, Action, Args &&...>::call( - std::forward(args)...); - default: - throw std::runtime_error( - "Internal error: Encountered unknown datatype (switchType) ->" + - std::to_string(static_cast(dt))); - } -} -/** - * Generalizes switching over an openPMD datatype. - * - * Will call the function template found at Action::call< T >(), instantiating T - * with the C++ internal datatype corresponding to the openPMD datatype. - * Ignores vector and array types. - * - * @tparam ReturnType The function template's return type. - * @tparam Action The struct containing the function template. - * @tparam Args The function template's argument types. - * @param dt The openPMD datatype. - * @param args The function template's arguments. - * @return Passes on the result of invoking the function template with the given - * arguments and with the template parameter specified by dt. - */ -template -auto switchNonVectorType(Datatype dt, Args &&...args) - -> decltype(Action::template call(std::forward(args)...)) -{ - using ReturnType = - decltype(Action::template call(std::forward(args)...)); - switch (dt) - { - case Datatype::CHAR: - return Action::template call(std::forward(args)...); - case Datatype::UCHAR: - return Action::template call( - std::forward(args)...); - case Datatype::SCHAR: - return Action::template call(std::forward(args)...); - case Datatype::SHORT: - return Action::template call(std::forward(args)...); - case Datatype::INT: - return Action::template call(std::forward(args)...); - case Datatype::LONG: - return Action::template call(std::forward(args)...); - case Datatype::LONGLONG: - return Action::template call(std::forward(args)...); - case Datatype::USHORT: - return Action::template call( - std::forward(args)...); - case Datatype::UINT: - return Action::template call(std::forward(args)...); - case Datatype::ULONG: - return Action::template call( - std::forward(args)...); - case Datatype::ULONGLONG: - return Action::template call( - std::forward(args)...); - case Datatype::FLOAT: - return Action::template call(std::forward(args)...); - case Datatype::DOUBLE: - return Action::template call(std::forward(args)...); - case Datatype::LONG_DOUBLE: - return Action::template call(std::forward(args)...); - case Datatype::CFLOAT: - return Action::template call >( - std::forward(args)...); - case Datatype::CDOUBLE: - return Action::template call >( - std::forward(args)...); - case Datatype::CLONG_DOUBLE: - return Action::template call >( - std::forward(args)...); - case Datatype::STRING: - return Action::template call(std::forward(args)...); - case Datatype::BOOL: - return Action::template call(std::forward(args)...); - case Datatype::UNDEFINED: - return detail:: - CallUndefinedDatatype<0, ReturnType, Action, Args &&...>::call( - std::forward(args)...); - default: - throw std::runtime_error( - "Internal error: Encountered unknown datatype (switchType) ->" + - std::to_string(static_cast(dt))); - } -} -} // namespace openPMD +#include "openPMD/Datatype.hpp" diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp index 00c67dcf2a..dc796c8180 100644 --- a/include/openPMD/RecordComponent.tpp +++ b/include/openPMD/RecordComponent.tpp @@ -100,7 +100,8 @@ RecordComponent::loadChunk(std::shared_ptr data, Offset o, Extent e) if (dtype != getDatatype()) if (!isSameInteger(getDatatype()) && !isSameFloatingPoint(getDatatype()) && - !isSameComplexFloatingPoint(getDatatype())) + !isSameComplexFloatingPoint(getDatatype()) && + !isSameChar(getDatatype())) { std::string const data_type_str = datatypeToString(getDatatype()); std::string const requ_type_str = diff --git a/src/IO/HDF5/HDF5Auxiliary.cpp b/src/IO/HDF5/HDF5Auxiliary.cpp index 3a6c9e7cd4..53fb1fb390 100644 --- a/src/IO/HDF5/HDF5Auxiliary.cpp +++ b/src/IO/HDF5/HDF5Auxiliary.cpp @@ -56,15 +56,16 @@ hid_t openPMD::GetH5DataType::operator()(Attribute const &att) using DT = Datatype; switch (att.dtype) { - case DT::CHAR: - case DT::VEC_CHAR: - return H5Tcopy(H5T_NATIVE_CHAR); case DT::UCHAR: case DT::VEC_UCHAR: return H5Tcopy(H5T_NATIVE_UCHAR); case DT::SCHAR: case DT::VEC_SCHAR: return H5Tcopy(H5T_NATIVE_SCHAR); + // NOTE: in HDF5, CHAR is actually either UCHAR or SCHAR. + case DT::CHAR: + case DT::VEC_CHAR: + return H5Tcopy(H5T_NATIVE_CHAR); case DT::SHORT: case DT::VEC_SHORT: return H5Tcopy(H5T_NATIVE_SHORT); diff --git a/src/IO/HDF5/HDF5IOHandler.cpp b/src/IO/HDF5/HDF5IOHandler.cpp index 00f2f0245e..24aa6cac2d 100644 --- a/src/IO/HDF5/HDF5IOHandler.cpp +++ b/src/IO/HDF5/HDF5IOHandler.cpp @@ -987,12 +987,13 @@ void HDF5IOHandlerImpl::openDataset( if (dataset_class == H5S_SIMPLE || dataset_class == H5S_SCALAR || dataset_class == H5S_NULL) { - if (H5Tequal(dataset_type, H5T_NATIVE_CHAR)) - d = DT::CHAR; - else if (H5Tequal(dataset_type, H5T_NATIVE_UCHAR)) + if (H5Tequal(dataset_type, H5T_NATIVE_UCHAR)) d = DT::UCHAR; else if (H5Tequal(dataset_type, H5T_NATIVE_SCHAR)) d = DT::SCHAR; + // NOTE: in HDF5, CHAR is actually either UCHAR or SCHAR. + else if (H5Tequal(dataset_type, H5T_NATIVE_CHAR)) + d = DT::CHAR; else if (H5Tequal(dataset_type, H5T_NATIVE_SHORT)) d = DT::SHORT; else if (H5Tequal(dataset_type, H5T_NATIVE_INT)) diff --git a/test/SerialIOTest.cpp b/test/SerialIOTest.cpp index ce37e4f352..49e070e312 100644 --- a/test/SerialIOTest.cpp +++ b/test/SerialIOTest.cpp @@ -1,4 +1,6 @@ // expose private and protected members for invasive testing +#include "openPMD/Datatype.hpp" +#include "openPMD/IO/Access.hpp" #if openPMD_USE_INVASIVE_TESTS #define OPENPMD_private public: #define OPENPMD_protected public: @@ -208,6 +210,54 @@ TEST_CASE("adios2_char_portability", "[serial][adios2]") } #endif +namespace detail +{ +template +void writeChar(Series &series, std::string const &component_name) +{ + auto component = series.iterations[0].meshes["E"][component_name]; + std::vector data(10); + component.resetDataset({determineDatatype(), {10}}); + component.storeChunk(data, {0}, {10}); + series.flush(); +} +template +void readChar(Series &series, std::string const &component_name) +{ + auto component = series.iterations[0].meshes["E"][component_name]; + std::vector data(10); + auto chunk = component.loadChunk(); + series.flush(); + for (size_t i = 0; i < 10; ++i) + { + REQUIRE(data[i] == chunk.get()[i]); + } +} +} // namespace detail + +void char_roundtrip(std::string const &extension) +{ + Series write("../samples/char_rountrip." + extension, Access::CREATE); + ::detail::writeChar(write, "char"); + ::detail::writeChar(write, "uchar"); + ::detail::writeChar(write, "schar"); + write.close(); + + Series read("../samples/char_rountrip." + extension, Access::READ_ONLY); + ::detail::readChar(read, "char"); + ::detail::readChar(read, "uchar"); + ::detail::readChar(read, "schar"); + read.close(); +} + +TEST_CASE("char_roundtrip", "[serial]") +{ + for (auto const &t : testedFileExtensions()) + { + char_roundtrip(t); + } +} + void write_and_read_many_iterations( std::string const &ext, bool intermittentFlushes) {