From 653054a0d73f19b78241003372c6eb8680a320a5 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Sat, 11 May 2024 08:37:42 +0200 Subject: [PATCH 1/3] Move test code: strings and empty arrays. --- tests/unit/CMakeLists.txt | 2 +- tests/unit/test_empty_arrays.cpp | 248 +++++++++++++ tests/unit/test_string.cpp | 351 ++++++++++++++++++ tests/unit/tests_high_five_base.cpp | 546 ---------------------------- 4 files changed, 600 insertions(+), 547 deletions(-) create mode 100644 tests/unit/test_empty_arrays.cpp create mode 100644 tests/unit/test_string.cpp diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index ab7c65749..980fe077a 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -6,7 +6,7 @@ if(MSVC) endif() ## Base tests -foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_legacy) +foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_empty_arrays test_legacy test_string) add_executable(${test_name} "${test_name}.cpp") target_link_libraries(${test_name} HighFive HighFiveWarnings HighFiveFlags Catch2::Catch2WithMain) target_link_libraries(${test_name} HighFiveOptionalDependencies) diff --git a/tests/unit/test_empty_arrays.cpp b/tests/unit/test_empty_arrays.cpp new file mode 100644 index 000000000..ea447ffaf --- /dev/null +++ b/tests/unit/test_empty_arrays.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c), 2017-2024, Blue Brain Project - EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ +#include +#include + +#include +#include +#include + +#include +#include "tests_high_five.hpp" +#include "create_traits.hpp" + +#ifdef HIGHFIVE_TEST_BOOST +#include +#endif + +#ifdef HIGHFIVE_TEST_EIGEN +#include +#endif + +#ifdef HIGHFIVE_TEST_SPAN +#include +#endif + +using namespace HighFive; +using Catch::Matchers::Equals; + + +template +struct CreateEmptyVector; + +template <> +struct CreateEmptyVector<1> { + using container_type = std::vector; + + static container_type create(const std::vector& dims) { + return container_type(dims[0], 2); + } +}; + +template +struct CreateEmptyVector { + using container_type = std::vector::container_type>; + + static container_type create(const std::vector& dims) { + auto subdims = std::vector(dims.begin() + 1, dims.end()); + return container_type(dims[0], CreateEmptyVector::create(subdims)); + } +}; + +#ifdef HIGHFIVE_TEST_BOOST +template +struct CreateEmptyBoostMultiArray { + using container_type = boost::multi_array(n_dim)>; + + static container_type create(const std::vector& dims) { + auto container = container_type(dims); + + auto raw_data = std::vector(compute_total_size(dims)); + container.assign(raw_data.begin(), raw_data.end()); + + return container; + } +}; +#endif + + +#ifdef HIGHFIVE_TEST_EIGEN +struct CreateEmptyEigenVector { + using container_type = Eigen::VectorXi; + + static container_type create(const std::vector& dims) { + return container_type::Constant(int(dims[0]), 2); + } +}; + +struct CreateEmptyEigenMatrix { + using container_type = Eigen::MatrixXi; + + static container_type create(const std::vector& dims) { + return container_type::Constant(int(dims[0]), int(dims[1]), 2); + } +}; +#endif + +template +void check_empty_dimensions(const Container& container, const std::vector& expected_dims) { + auto deduced_dims = details::inspector::getDimensions(container); + + REQUIRE(expected_dims.size() == deduced_dims.size()); + + // The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1` + // or what the original dims said. The `1` allows broadcasting, the "same as original" enables + // statically sized objects, which conceptually have dims, even if there's no object. + bool allow_one = false; + for (size_t i = 0; i < expected_dims.size(); ++i) { + REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul)))); + + if (expected_dims[i] == 0) { + allow_one = true; + } + } +} + +template +void check_empty_dimensions(const std::vector& dims) { + auto input_data = CreateContainer::create(dims); + check_empty_dimensions(input_data, dims); +} + +template +void check_empty_read_write_cycle(const std::vector& dims) { + using container_type = typename CreateContainer::container_type; + + const std::string file_name("h5_empty_attr.h5"); + const std::string dataset_name("dset"); + File file(file_name, File::Truncate); + + auto input_data = CreateContainer::create(dims); + ReadWriteInterface::create(file, dataset_name, input_data); + + SECTION("read; one-dimensional vector (empty)") { + auto output_data = CreateEmptyVector<1>::create({0ul}); + + ReadWriteInterface::get(file, dataset_name).reshapeMemSpace({0ul}).read(output_data); + check_empty_dimensions(output_data, {0ul}); + } + + SECTION("read; pre-allocated (empty)") { + auto output_data = CreateContainer::create(dims); + ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data); + + check_empty_dimensions(output_data, dims); + } + + SECTION("read; pre-allocated (oversized)") { + auto oversize_dims = std::vector(dims.size(), 2ul); + auto output_data = CreateContainer::create(oversize_dims); + ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data); + + check_empty_dimensions(output_data, dims); + } + + SECTION("read; auto-allocated") { + auto output_data = ReadWriteInterface::get(file, dataset_name) + .reshapeMemSpace(dims) + .template read(); + check_empty_dimensions(output_data, dims); + } +} + +template +void check_empty_dataset(const std::vector& dims) { + check_empty_read_write_cycle(dims); +} + +template +void check_empty_attribute(const std::vector& dims) { + check_empty_read_write_cycle(dims); +} + +template +void check_empty_everything(const std::vector& dims) { + SECTION("Empty dimensions") { + check_empty_dimensions(dims); + } + + SECTION("Empty datasets") { + check_empty_dataset(dims); + } + + SECTION("Empty attribute") { + check_empty_attribute(dims); + } +} + +#ifdef HIGHFIVE_TEST_EIGEN +template +void check_empty_eigen(const std::vector&) {} + +template <> +void check_empty_eigen<1>(const std::vector& dims) { + SECTION("Eigen::Vector") { + check_empty_everything({dims[0], 1ul}); + } +} + +template <> +void check_empty_eigen<2>(const std::vector& dims) { + SECTION("Eigen::Matrix") { + check_empty_everything(dims); + } +} +#endif + +template +void check_empty(const std::vector& dims) { + REQUIRE(dims.size() == ndim); + + SECTION("std::vector") { + check_empty_everything>(dims); + } + +#ifdef HIGHFIVE_TEST_BOOST + SECTION("boost::multi_array") { + check_empty_everything>(dims); + } +#endif + +#ifdef HIGHFIVE_TEST_EIGEN + check_empty_eigen(dims); +#endif +} + +TEST_CASE("Empty arrays") { + SECTION("one-dimensional") { + check_empty<1>({0ul}); + } + + SECTION("two-dimensional") { + std::vector> testcases{{0ul, 1ul}, {1ul, 0ul}}; + + for (const auto& dims: testcases) { + SECTION(details::format_vector(dims)) { + check_empty<2>(dims); + } + } + } + + SECTION("three-dimensional") { + std::vector> testcases{{0ul, 1ul, 1ul}, + {1ul, 1ul, 0ul}, + {1ul, 0ul, 1ul}}; + + for (const auto& dims: testcases) { + SECTION(details::format_vector(dims)) { + check_empty<3>(dims); + } + } + } +} diff --git a/tests/unit/test_string.cpp b/tests/unit/test_string.cpp new file mode 100644 index 000000000..ed6a4a5bf --- /dev/null +++ b/tests/unit/test_string.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (c), 2017-2024, Blue Brain Project - EPFL + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +#include +#include +#include + +#include +#include "tests_high_five.hpp" +#include "create_traits.hpp" + + +using namespace HighFive; +using Catch::Matchers::Equals; + +TEST_CASE("StringType") { + SECTION("enshrine-defaults") { + auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded); + auto variable_length = VariableLengthStringType(); + + REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii); + REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii); + } + + SECTION("fixed-length") { + auto fixed_length = + FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8); + auto string_type = fixed_length.asStringType(); + + REQUIRE(string_type.getId() == fixed_length.getId()); + REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); + REQUIRE(string_type.getPadding() == StringPadding::SpacePadded); + REQUIRE(string_type.getSize() == 32); + REQUIRE(!string_type.isVariableStr()); + REQUIRE(string_type.isFixedLenStr()); + } + + SECTION("variable-length") { + auto variable_length = VariableLengthStringType(CharacterSet::Utf8); + auto string_type = variable_length.asStringType(); + + REQUIRE(string_type.getId() == variable_length.getId()); + REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); + REQUIRE(string_type.isVariableStr()); + REQUIRE(!string_type.isFixedLenStr()); + } + + SECTION("atomic") { + auto atomic = AtomicType(); + REQUIRE_THROWS(atomic.asStringType()); + } +} + +template +void check_single_string(File file, size_t string_length) { + auto value = std::string(string_length, 'o'); + auto dataspace = DataSpace::From(value); + + auto n_chars = value.size() + 1; + auto n_chars_overlength = n_chars + 10; + auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto overlength_nullterm = FixedLengthStringType(n_chars_overlength, + StringPadding::NullTerminated); + auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded); + auto overlength_spacepad = FixedLengthStringType(n_chars_overlength, + StringPadding::SpacePadded); + auto variable_length = VariableLengthStringType(); + + SECTION("automatic") { + auto obj = CreateTraits::create(file, "auto", value); + REQUIRE(obj.template read() == value); + } + + SECTION("fixed length") { + auto obj = CreateTraits::create(file, "fixed", dataspace, fixed_length); + obj.write(value); + REQUIRE(obj.template read() == value); + } + + SECTION("overlength null-terminated") { + auto obj = + CreateTraits::create(file, "overlength_nullterm", dataspace, overlength_nullterm); + obj.write(value); + REQUIRE(obj.template read() == value); + } + + SECTION("overlength null-padded") { + auto obj = CreateTraits::create(file, "overlength_nullpad", dataspace, overlength_nullpad); + obj.write(value); + auto expected = std::string(n_chars_overlength, '\0'); + expected.replace(0, value.size(), value.data()); + REQUIRE(obj.template read() == expected); + } + + SECTION("overlength space-padded") { + auto obj = + CreateTraits::create(file, "overlength_spacepad", dataspace, overlength_spacepad); + obj.write(value); + auto expected = std::string(n_chars_overlength, ' '); + expected.replace(0, value.size(), value.data()); + REQUIRE(obj.template read() == expected); + } + + SECTION("variable length") { + auto obj = CreateTraits::create(file, "variable", dataspace, variable_length); + obj.write(value); + REQUIRE(obj.template read() == value); + } +} + +template +void check_multiple_string(File file, size_t string_length) { + using value_t = std::vector; + auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')}; + + auto dataspace = DataSpace::From(value); + + auto string_overlength = string_length + 10; + auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded); + auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded); + + auto overlength_nullterm = FixedLengthStringType(string_overlength, + StringPadding::NullTerminated); + auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded); + auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded); + auto variable_length = VariableLengthStringType(); + + auto check = [](const value_t actual, const value_t& expected) { + REQUIRE(actual.size() == expected.size()); + for (size_t i = 0; i < actual.size(); ++i) { + REQUIRE(actual[i] == expected[i]); + } + }; + + SECTION("automatic") { + auto obj = CreateTraits::create(file, "auto", value); + check(obj.template read(), value); + } + + SECTION("variable length") { + auto obj = CreateTraits::create(file, "variable", dataspace, variable_length); + obj.write(value); + check(obj.template read(), value); + } + + auto make_padded_reference = [&](char pad, size_t n) { + auto expected = std::vector(value.size(), std::string(n, pad)); + for (size_t i = 0; i < value.size(); ++i) { + expected[i].replace(0, value[i].size(), value[i].data()); + } + + return expected; + }; + + auto check_fixed_length = [&](const std::string& label, size_t length) { + SECTION(label + " null-terminated") { + auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated); + auto obj = CreateTraits::create(file, label + "_nullterm", dataspace, datatype); + obj.write(value); + check(obj.template read(), value); + } + + SECTION(label + " null-padded") { + auto datatype = FixedLengthStringType(length, StringPadding::NullPadded); + auto obj = CreateTraits::create(file, label + "_nullpad", dataspace, datatype); + obj.write(value); + auto expected = make_padded_reference('\0', length); + check(obj.template read(), expected); + } + + SECTION(label + " space-padded") { + auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded); + auto obj = CreateTraits::create(file, label + "_spacepad", dataspace, datatype); + obj.write(value); + auto expected = make_padded_reference(' ', length); + check(obj.template read(), expected); + } + }; + + check_fixed_length("onpoint", string_length); + check_fixed_length("overlength", string_length + 5); + + + SECTION("underlength null-terminated") { + auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated); + auto obj = CreateTraits::create(file, "underlength_nullterm", dataspace, datatype); + REQUIRE_THROWS(obj.write(value)); + } + + SECTION("underlength nullpad") { + auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded); + auto obj = CreateTraits::create(file, "underlength_nullpad", dataspace, datatype); + REQUIRE_THROWS(obj.write(value)); + } + + SECTION("underlength spacepad") { + auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated); + auto obj = CreateTraits::create(file, "underlength_spacepad", dataspace, datatype); + REQUIRE_THROWS(obj.write(value)); + } +} + +TEST_CASE("HighFiveSTDString (dataset, single, short)") { + File file("std_string_dataset_single_short.h5", File::Truncate); + check_single_string(file, 3); +} + +TEST_CASE("HighFiveSTDString (attribute, single, short)") { + File file("std_string_attribute_single_short.h5", File::Truncate); + check_single_string(file, 3); +} + +TEST_CASE("HighFiveSTDString (dataset, single, long)") { + File file("std_string_dataset_single_long.h5", File::Truncate); + check_single_string(file, 256); +} + +TEST_CASE("HighFiveSTDString (attribute, single, long)") { + File file("std_string_attribute_single_long.h5", File::Truncate); + check_single_string(file, 256); +} + +TEST_CASE("HighFiveSTDString (dataset, multiple, short)") { + File file("std_string_dataset_multiple_short.h5", File::Truncate); + check_multiple_string(file, 3); +} + +TEST_CASE("HighFiveSTDString (attribute, multiple, short)") { + File file("std_string_attribute_multiple_short.h5", File::Truncate); + check_multiple_string(file, 3); +} + +TEST_CASE("HighFiveSTDString (dataset, multiple, long)") { + File file("std_string_dataset_multiple_long.h5", File::Truncate); + check_multiple_string(file, 256); +} + +TEST_CASE("HighFiveSTDString (attribute, multiple, long)") { + File file("std_string_attribute_multiple_long.h5", File::Truncate); + check_multiple_string(file, 256); +} + +TEST_CASE("HighFiveFixedString") { + const std::string file_name("array_atomic_types.h5"); + const std::string group_1("group1"); + + // Create a new file using the default property lists. + File file(file_name, File::ReadWrite | File::Create | File::Truncate); + char raw_strings[][10] = {"abcd", "1234"}; + + /// This will not compile - only char arrays - hits static_assert with a nice + /// error + // file.createDataSet(ds_name, DataSpace(2))); + + { // But char should be fine + auto ds = file.createDataSet("ds1", DataSpace(2)); + CHECK(ds.getDataType().getClass() == DataTypeClass::String); + ds.write(raw_strings); + } + + { // char[] is, by default, int8 + auto ds2 = file.createDataSet("ds2", raw_strings); + CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer); + } + + { // String Truncate happens low-level if well setup + auto ds3 = file.createDataSet("ds3", DataSpace::FromCharArrayStrings(raw_strings)); + ds3.write(raw_strings); + } + + { // Write as raw elements from pointer (with const) + const char(*strings_fixed)[10] = raw_strings; + // With a pointer we dont know how many strings -> manual DataSpace + file.createDataSet("ds4", DataSpace(2)).write(strings_fixed); + } + + + { // Cant convert flex-length to fixed-length + const char* buffer[] = {"abcd", "1234"}; + SilenceHDF5 silencer; + CHECK_THROWS_AS(file.createDataSet("ds5", DataSpace(2)).write(buffer), + HighFive::DataSetException); + } + + { // scalar char strings + const char buffer[] = "abcd"; + file.createDataSet("ds6", DataSpace(1)).write(buffer); + } + + { + // Direct way of writing `std::string` as a fixed length + // HDF5 string. + + std::string value = "foo"; + auto n_chars = value.size() + 1; + + auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto dataspace = DataSpace(1); + + auto ds = file.createDataSet("ds8", dataspace, datatype); + ds.write_raw(value.data(), datatype); + + { + // Due to missing non-const overload of `data()` until C++17 we'll + // read into something else instead (don't forget the '\0'). + auto expected = std::vector(n_chars, '!'); + ds.read_raw(expected.data(), datatype); + + CHECK(expected.size() == value.size() + 1); + for (size_t i = 0; i < value.size(); ++i) { + REQUIRE(expected[i] == value[i]); + } + } + +#if HIGHFIVE_CXX_STD >= 17 + { + auto expected = std::string(value.size(), '-'); + ds.read_raw(expected.data(), datatype); + + REQUIRE(expected == value); + } +#endif + } + + { + size_t n_chars = 4; + size_t n_strings = 2; + + std::vector value(n_chars * n_strings, '!'); + + auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); + auto dataspace = DataSpace(n_strings); + + auto ds = file.createDataSet("ds9", dataspace, datatype); + ds.write_raw(value.data(), datatype); + + auto expected = std::vector(value.size(), '-'); + ds.read_raw(expected.data(), datatype); + + CHECK(expected.size() == value.size()); + for (size_t i = 0; i < value.size(); ++i) { + REQUIRE(expected[i] == value[i]); + } + } +} diff --git a/tests/unit/tests_high_five_base.cpp b/tests/unit/tests_high_five_base.cpp index 16da4bf80..609c6a7f8 100644 --- a/tests/unit/tests_high_five_base.cpp +++ b/tests/unit/tests_high_five_base.cpp @@ -819,45 +819,6 @@ TEST_CASE("Test simple listings") { } } -TEST_CASE("StringType") { - SECTION("enshrine-defaults") { - auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded); - auto variable_length = VariableLengthStringType(); - - REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii); - REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii); - } - - SECTION("fixed-length") { - auto fixed_length = - FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8); - auto string_type = fixed_length.asStringType(); - - REQUIRE(string_type.getId() == fixed_length.getId()); - REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); - REQUIRE(string_type.getPadding() == StringPadding::SpacePadded); - REQUIRE(string_type.getSize() == 32); - REQUIRE(!string_type.isVariableStr()); - REQUIRE(string_type.isFixedLenStr()); - } - - SECTION("variable-length") { - auto variable_length = VariableLengthStringType(CharacterSet::Utf8); - auto string_type = variable_length.asStringType(); - - REQUIRE(string_type.getId() == variable_length.getId()); - REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8); - REQUIRE(string_type.isVariableStr()); - REQUIRE(!string_type.isFixedLenStr()); - } - - SECTION("atomic") { - auto atomic = AtomicType(); - REQUIRE_THROWS(atomic.asStringType()); - } -} - - TEST_CASE("DataTypeEqualTakeBack") { const std::string file_name("h5tutr_dset.h5"); const std::string dataset_name("dset"); @@ -1687,220 +1648,6 @@ TEST_CASE("Modify Mem Space, dset") { } -template -struct CreateEmptyVector; - -template <> -struct CreateEmptyVector<1> { - using container_type = std::vector; - - static container_type create(const std::vector& dims) { - return container_type(dims[0], 2); - } -}; - -template -struct CreateEmptyVector { - using container_type = std::vector::container_type>; - - static container_type create(const std::vector& dims) { - auto subdims = std::vector(dims.begin() + 1, dims.end()); - return container_type(dims[0], CreateEmptyVector::create(subdims)); - } -}; - -#ifdef HIGHFIVE_TEST_BOOST -template -struct CreateEmptyBoostMultiArray { - using container_type = boost::multi_array(n_dim)>; - - static container_type create(const std::vector& dims) { - auto container = container_type(dims); - - auto raw_data = std::vector(compute_total_size(dims)); - container.assign(raw_data.begin(), raw_data.end()); - - return container; - } -}; -#endif - - -#ifdef HIGHFIVE_TEST_EIGEN -struct CreateEmptyEigenVector { - using container_type = Eigen::VectorXi; - - static container_type create(const std::vector& dims) { - return container_type::Constant(int(dims[0]), 2); - } -}; - -struct CreateEmptyEigenMatrix { - using container_type = Eigen::MatrixXi; - - static container_type create(const std::vector& dims) { - return container_type::Constant(int(dims[0]), int(dims[1]), 2); - } -}; -#endif - -template -void check_empty_dimensions(const Container& container, const std::vector& expected_dims) { - auto deduced_dims = details::inspector::getDimensions(container); - - REQUIRE(expected_dims.size() == deduced_dims.size()); - - // The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1` - // or what the original dims said. The `1` allows broadcasting, the "same as original" enables - // statically sized objects, which conceptually have dims, even if there's no object. - bool allow_one = false; - for (size_t i = 0; i < expected_dims.size(); ++i) { - REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul)))); - - if (expected_dims[i] == 0) { - allow_one = true; - } - } -} - -template -void check_empty_dimensions(const std::vector& dims) { - auto input_data = CreateContainer::create(dims); - check_empty_dimensions(input_data, dims); -} - -template -void check_empty_read_write_cycle(const std::vector& dims) { - using container_type = typename CreateContainer::container_type; - - const std::string file_name("h5_empty_attr.h5"); - const std::string dataset_name("dset"); - File file(file_name, File::Truncate); - - auto input_data = CreateContainer::create(dims); - ReadWriteInterface::create(file, dataset_name, input_data); - - SECTION("read; one-dimensional vector (empty)") { - auto output_data = CreateEmptyVector<1>::create({0ul}); - - ReadWriteInterface::get(file, dataset_name).reshapeMemSpace({0ul}).read(output_data); - check_empty_dimensions(output_data, {0ul}); - } - - SECTION("read; pre-allocated (empty)") { - auto output_data = CreateContainer::create(dims); - ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data); - - check_empty_dimensions(output_data, dims); - } - - SECTION("read; pre-allocated (oversized)") { - auto oversize_dims = std::vector(dims.size(), 2ul); - auto output_data = CreateContainer::create(oversize_dims); - ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data); - - check_empty_dimensions(output_data, dims); - } - - SECTION("read; auto-allocated") { - auto output_data = ReadWriteInterface::get(file, dataset_name) - .reshapeMemSpace(dims) - .template read(); - check_empty_dimensions(output_data, dims); - } -} - -template -void check_empty_dataset(const std::vector& dims) { - check_empty_read_write_cycle(dims); -} - -template -void check_empty_attribute(const std::vector& dims) { - check_empty_read_write_cycle(dims); -} - -template -void check_empty_everything(const std::vector& dims) { - SECTION("Empty dimensions") { - check_empty_dimensions(dims); - } - - SECTION("Empty datasets") { - check_empty_dataset(dims); - } - - SECTION("Empty attribute") { - check_empty_attribute(dims); - } -} - -#ifdef HIGHFIVE_TEST_EIGEN -template -void check_empty_eigen(const std::vector&) {} - -template <> -void check_empty_eigen<1>(const std::vector& dims) { - SECTION("Eigen::Vector") { - check_empty_everything({dims[0], 1ul}); - } -} - -template <> -void check_empty_eigen<2>(const std::vector& dims) { - SECTION("Eigen::Matrix") { - check_empty_everything(dims); - } -} -#endif - -template -void check_empty(const std::vector& dims) { - REQUIRE(dims.size() == ndim); - - SECTION("std::vector") { - check_empty_everything>(dims); - } - -#ifdef HIGHFIVE_TEST_BOOST - SECTION("boost::multi_array") { - check_empty_everything>(dims); - } -#endif - -#ifdef HIGHFIVE_TEST_EIGEN - check_empty_eigen(dims); -#endif -} - -TEST_CASE("Empty arrays") { - SECTION("one-dimensional") { - check_empty<1>({0ul}); - } - - SECTION("two-dimensional") { - std::vector> testcases{{0ul, 1ul}, {1ul, 0ul}}; - - for (const auto& dims: testcases) { - SECTION(details::format_vector(dims)) { - check_empty<2>(dims); - } - } - } - - SECTION("three-dimensional") { - std::vector> testcases{{0ul, 1ul, 1ul}, - {1ul, 1ul, 0ul}, - {1ul, 0ul, 1ul}}; - - for (const auto& dims: testcases) { - SECTION(details::format_vector(dims)) { - check_empty<3>(dims); - } - } - } -} - TEST_CASE("HighFiveRecursiveGroups") { const std::string file_name("h5_ds_exist.h5"); const std::string group_1("group1"); @@ -2315,299 +2062,6 @@ TEST_CASE("DirectWriteBool") { } -template -void check_single_string(File file, size_t string_length) { - auto value = std::string(string_length, 'o'); - auto dataspace = DataSpace::From(value); - - auto n_chars = value.size() + 1; - auto n_chars_overlength = n_chars + 10; - auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated); - auto overlength_nullterm = FixedLengthStringType(n_chars_overlength, - StringPadding::NullTerminated); - auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded); - auto overlength_spacepad = FixedLengthStringType(n_chars_overlength, - StringPadding::SpacePadded); - auto variable_length = VariableLengthStringType(); - - SECTION("automatic") { - auto obj = CreateTraits::create(file, "auto", value); - REQUIRE(obj.template read() == value); - } - - SECTION("fixed length") { - auto obj = CreateTraits::create(file, "fixed", dataspace, fixed_length); - obj.write(value); - REQUIRE(obj.template read() == value); - } - - SECTION("overlength null-terminated") { - auto obj = - CreateTraits::create(file, "overlength_nullterm", dataspace, overlength_nullterm); - obj.write(value); - REQUIRE(obj.template read() == value); - } - - SECTION("overlength null-padded") { - auto obj = CreateTraits::create(file, "overlength_nullpad", dataspace, overlength_nullpad); - obj.write(value); - auto expected = std::string(n_chars_overlength, '\0'); - expected.replace(0, value.size(), value.data()); - REQUIRE(obj.template read() == expected); - } - - SECTION("overlength space-padded") { - auto obj = - CreateTraits::create(file, "overlength_spacepad", dataspace, overlength_spacepad); - obj.write(value); - auto expected = std::string(n_chars_overlength, ' '); - expected.replace(0, value.size(), value.data()); - REQUIRE(obj.template read() == expected); - } - - SECTION("variable length") { - auto obj = CreateTraits::create(file, "variable", dataspace, variable_length); - obj.write(value); - REQUIRE(obj.template read() == value); - } -} - -template -void check_multiple_string(File file, size_t string_length) { - using value_t = std::vector; - auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')}; - - auto dataspace = DataSpace::From(value); - - auto string_overlength = string_length + 10; - auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded); - auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded); - - auto overlength_nullterm = FixedLengthStringType(string_overlength, - StringPadding::NullTerminated); - auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded); - auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded); - auto variable_length = VariableLengthStringType(); - - auto check = [](const value_t actual, const value_t& expected) { - REQUIRE(actual.size() == expected.size()); - for (size_t i = 0; i < actual.size(); ++i) { - REQUIRE(actual[i] == expected[i]); - } - }; - - SECTION("automatic") { - auto obj = CreateTraits::create(file, "auto", value); - check(obj.template read(), value); - } - - SECTION("variable length") { - auto obj = CreateTraits::create(file, "variable", dataspace, variable_length); - obj.write(value); - check(obj.template read(), value); - } - - auto make_padded_reference = [&](char pad, size_t n) { - auto expected = std::vector(value.size(), std::string(n, pad)); - for (size_t i = 0; i < value.size(); ++i) { - expected[i].replace(0, value[i].size(), value[i].data()); - } - - return expected; - }; - - auto check_fixed_length = [&](const std::string& label, size_t length) { - SECTION(label + " null-terminated") { - auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated); - auto obj = CreateTraits::create(file, label + "_nullterm", dataspace, datatype); - obj.write(value); - check(obj.template read(), value); - } - - SECTION(label + " null-padded") { - auto datatype = FixedLengthStringType(length, StringPadding::NullPadded); - auto obj = CreateTraits::create(file, label + "_nullpad", dataspace, datatype); - obj.write(value); - auto expected = make_padded_reference('\0', length); - check(obj.template read(), expected); - } - - SECTION(label + " space-padded") { - auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded); - auto obj = CreateTraits::create(file, label + "_spacepad", dataspace, datatype); - obj.write(value); - auto expected = make_padded_reference(' ', length); - check(obj.template read(), expected); - } - }; - - check_fixed_length("onpoint", string_length); - check_fixed_length("overlength", string_length + 5); - - - SECTION("underlength null-terminated") { - auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated); - auto obj = CreateTraits::create(file, "underlength_nullterm", dataspace, datatype); - REQUIRE_THROWS(obj.write(value)); - } - - SECTION("underlength nullpad") { - auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded); - auto obj = CreateTraits::create(file, "underlength_nullpad", dataspace, datatype); - REQUIRE_THROWS(obj.write(value)); - } - - SECTION("underlength spacepad") { - auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated); - auto obj = CreateTraits::create(file, "underlength_spacepad", dataspace, datatype); - REQUIRE_THROWS(obj.write(value)); - } -} - -TEST_CASE("HighFiveSTDString (dataset, single, short)") { - File file("std_string_dataset_single_short.h5", File::Truncate); - check_single_string(file, 3); -} - -TEST_CASE("HighFiveSTDString (attribute, single, short)") { - File file("std_string_attribute_single_short.h5", File::Truncate); - check_single_string(file, 3); -} - -TEST_CASE("HighFiveSTDString (dataset, single, long)") { - File file("std_string_dataset_single_long.h5", File::Truncate); - check_single_string(file, 256); -} - -TEST_CASE("HighFiveSTDString (attribute, single, long)") { - File file("std_string_attribute_single_long.h5", File::Truncate); - check_single_string(file, 256); -} - -TEST_CASE("HighFiveSTDString (dataset, multiple, short)") { - File file("std_string_dataset_multiple_short.h5", File::Truncate); - check_multiple_string(file, 3); -} - -TEST_CASE("HighFiveSTDString (attribute, multiple, short)") { - File file("std_string_attribute_multiple_short.h5", File::Truncate); - check_multiple_string(file, 3); -} - -TEST_CASE("HighFiveSTDString (dataset, multiple, long)") { - File file("std_string_dataset_multiple_long.h5", File::Truncate); - check_multiple_string(file, 256); -} - -TEST_CASE("HighFiveSTDString (attribute, multiple, long)") { - File file("std_string_attribute_multiple_long.h5", File::Truncate); - check_multiple_string(file, 256); -} - -TEST_CASE("HighFiveFixedString") { - const std::string file_name("array_atomic_types.h5"); - const std::string group_1("group1"); - - // Create a new file using the default property lists. - File file(file_name, File::ReadWrite | File::Create | File::Truncate); - char raw_strings[][10] = {"abcd", "1234"}; - - /// This will not compile - only char arrays - hits static_assert with a nice - /// error - // file.createDataSet(ds_name, DataSpace(2))); - - { // But char should be fine - auto ds = file.createDataSet("ds1", DataSpace(2)); - CHECK(ds.getDataType().getClass() == DataTypeClass::String); - ds.write(raw_strings); - } - - { // char[] is, by default, int8 - auto ds2 = file.createDataSet("ds2", raw_strings); - CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer); - } - - { // String Truncate happens low-level if well setup - auto ds3 = file.createDataSet("ds3", DataSpace::FromCharArrayStrings(raw_strings)); - ds3.write(raw_strings); - } - - { // Write as raw elements from pointer (with const) - const char(*strings_fixed)[10] = raw_strings; - // With a pointer we dont know how many strings -> manual DataSpace - file.createDataSet("ds4", DataSpace(2)).write(strings_fixed); - } - - - { // Cant convert flex-length to fixed-length - const char* buffer[] = {"abcd", "1234"}; - SilenceHDF5 silencer; - CHECK_THROWS_AS(file.createDataSet("ds5", DataSpace(2)).write(buffer), - HighFive::DataSetException); - } - - { // scalar char strings - const char buffer[] = "abcd"; - file.createDataSet("ds6", DataSpace(1)).write(buffer); - } - - { - // Direct way of writing `std::string` as a fixed length - // HDF5 string. - - std::string value = "foo"; - auto n_chars = value.size() + 1; - - auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); - auto dataspace = DataSpace(1); - - auto ds = file.createDataSet("ds8", dataspace, datatype); - ds.write_raw(value.data(), datatype); - - { - // Due to missing non-const overload of `data()` until C++17 we'll - // read into something else instead (don't forget the '\0'). - auto expected = std::vector(n_chars, '!'); - ds.read_raw(expected.data(), datatype); - - CHECK(expected.size() == value.size() + 1); - for (size_t i = 0; i < value.size(); ++i) { - REQUIRE(expected[i] == value[i]); - } - } - -#if HIGHFIVE_CXX_STD >= 17 - { - auto expected = std::string(value.size(), '-'); - ds.read_raw(expected.data(), datatype); - - REQUIRE(expected == value); - } -#endif - } - - { - size_t n_chars = 4; - size_t n_strings = 2; - - std::vector value(n_chars * n_strings, '!'); - - auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated); - auto dataspace = DataSpace(n_strings); - - auto ds = file.createDataSet("ds9", dataspace, datatype); - ds.write_raw(value.data(), datatype); - - auto expected = std::vector(value.size(), '-'); - ds.read_raw(expected.data(), datatype); - - CHECK(expected.size() == value.size()); - for (size_t i = 0; i < value.size(); ++i) { - REQUIRE(expected[i] == value[i]); - } - } -} - TEST_CASE("HighFiveReference") { const std::string file_name("h5_ref_test.h5"); const std::string dataset1_name("dset1"); From be6f6c7f259819b6ed63e7367c59a2981caffa5e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Sat, 11 May 2024 17:27:13 +0200 Subject: [PATCH 2/3] Update Developer Guide. --- doc/developer_guide.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/developer_guide.md b/doc/developer_guide.md index 13e360fc3..7e8f4eb8e 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -195,8 +195,11 @@ Write-read cycles for scalar values should be implemented in Unit-tests related to checking that `DataType` API, go in `tests/unit/tests_high_data_type.cpp`. +#### Empty Arrays +Check related to empty arrays to in `tests/unit/test_empty_arrays.cpp`. + #### Selections -Anything selection related goes in `tests/unit/test_high_five_selection.cpp`. +Anything selection related goes in `tests/unit/test_string.cpp`. This includes things like `ElementSet` and `HyperSlab`. #### Strings From 80fdf9d2e675e487a6c374130faf7b08e44fcf76 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 13 May 2024 10:39:27 +0200 Subject: [PATCH 3/3] fixup: dev guide. --- doc/developer_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/developer_guide.md b/doc/developer_guide.md index 7e8f4eb8e..90867ca12 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -199,7 +199,7 @@ Unit-tests related to checking that `DataType` API, go in Check related to empty arrays to in `tests/unit/test_empty_arrays.cpp`. #### Selections -Anything selection related goes in `tests/unit/test_string.cpp`. +Anything selection related goes in `tests/unit/test_high_five_selection.cpp`. This includes things like `ElementSet` and `HyperSlab`. #### Strings @@ -207,7 +207,7 @@ Regular write-read cycles for strings are performed along with the other types, see above. This should cover compatibility of `std::string` with all containers. However, additional testing is required, e.g. character set, padding, fixed vs. variable length. These all go in -`tests/unit/test_high_five_string.cpp`. +`tests/unit/test_string.cpp`. #### Specific Tests For Optional Containers If containers, e.g. `Eigen::Matrix` require special checks those go in files