Skip to content

Commit

Permalink
Skeleton HDF5 wrapper class and failing tests.
Browse files Browse the repository at this point in the history
Basic class structure and some failing tests for TDD.
  • Loading branch information
samcunliffe committed Mar 9, 2023
1 parent bd8e5d1 commit 93f7fdf
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 6 deletions.
62 changes: 59 additions & 3 deletions tdms/include/hdf5_io.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,65 @@
/**
* @file hdf5_io.h
* @brief File I/O using HDF5
* @brief File I/O using HDF5.
*/
#pragma once
#include <string>
#include <vector>

#include <H5Cpp.h>

/**
* @brief Mode to write the file: R, RW, O.
*/
enum HDF5FileMode {
READONLY = 0,
READWRITE,
OVERWRITE
};

/**
* @brief A simple class to wrap the HDF5 I/O.
*/
class HDF5File {

public:
/** Default constructor: default filename and mode. */
HDF5File() : filename_("tdms.hdf5"), mode_(READONLY) { _open(); }

/** Normal constructor. */
HDF5File(const std::string &filename, HDF5FileMode mode) : filename_(filename), mode_(mode) {
_open();
}

/** Destructor, deletes pointers. */
~HDF5File();

/** Writes array to the file. */
void write();

/** reads data from the file. */
void read();

/**
* @brief Check file health.
*
* @param print_debug Optionally this function can print to the debug log.
* @return true If the file is OK.
* @return false If the file is not OK.
*/
bool isOK(bool print_debug=false);

private:
/** Common to both constructors: open/create the file and set file_ to point to it. */
void _open();
std::string filename_; /**< The file name. */
HDF5FileMode mode_; /**< The I/O mode: default is to create non-existing. */
H5::H5File *file_ = nullptr; /**< The H5 file itself. */
std::vector<H5::DataSet *> datasets_; /**< All datasets to be written to the file. */
};

/**
* @brief Test of HDF5 I/O
* @brief Example of HDF5 I/O... to be deleted.
* This function should not make it to a PR.
*/
void test_hdf5();
void example_hdf5();
63 changes: 60 additions & 3 deletions tdms/src/hdf5_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,68 @@

// std
#include <string>
#include <iostream>
#include <stdexcept>

// external libraries
// external
#include <H5Cpp.h>
#include <H5Fpublic.h>
#include <spdlog/spdlog.h>

// own headers

/**
* @brief Convert a HDF5FileMode to the #defined HDF5 file creation property.
* @note Internal function, only used in this file by HDF5File::_open.
*/
unsigned int convert_to_h5f_global(HDF5FileMode mode) {
switch (mode) {
case READONLY: return H5F_ACC_RDONLY;
case READWRITE: return H5F_ACC_RDWR;
case OVERWRITE: return H5F_ACC_TRUNC;
default: return std::numeric_limits<int>::max();
}
}

void HDF5File::_open() {
spdlog::trace("Opening file: {}, in mode: {}", filename_, static_cast<int>(mode_));
file_ = new H5::H5File(filename_.c_str(), convert_to_h5f_global(mode_));
return;
}

HDF5File::~HDF5File() {
file_->close();
for (auto ds: datasets_)
delete ds;
delete file_;
}

void HDF5File::write() {
for (int i=0; i<3; i++) {
std::cout << convert_to_h5f_global(static_cast<HDF5FileMode>(i)) << std::endl;
}
return;
}
void HDF5File::read() {
return;
}

bool HDF5File::isOK(bool print_debug) {

if (print_debug) {
// debug information
spdlog::debug("File is named: {}", filename_);
spdlog::debug("File mode: {}", static_cast<int>(mode_));
spdlog::debug("Internal H5::H5File address: {:p}", (void*)file_);
}

// tests here
if (file_ == nullptr) return false;

if (file_->getFileSize() <= 0) return false;

// passed all tests: it's ok
return true;
}


#define MAX_NAME_LENGTH 32
Expand All @@ -27,7 +84,7 @@ typedef struct {



void test_hdf5() {
void example_hdf5() {

// Data to write
PersonalInformation person_list[] = {
Expand Down
116 changes: 116 additions & 0 deletions tdms/tests/unit/test_hdf5_io.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* @file test_hdf5_io.cpp
* @brief Tests of the HDF5 file I/O functionality.
*/
#include "hdf5_io.h"

// std
#include <ctime>
#include <filesystem>
#include <random>

// external
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <spdlog/spdlog.h>

// tdms
#include "unit_test_utils.h"

using tdms_tests::create_tmp_dir; // unit_test_utils.h

TEST_CASE("Test file I/O construction/destruction.") {
// setup - temporary directory
auto tmp = create_tmp_dir();

SECTION("Check file I/O modes") {
// check possible file i/o modes: create file, then open same file readonly, then open
// rw, then create it again (even though it already exists)
for (auto i : {HDF5FileMode::OVERWRITE, HDF5FileMode::READONLY, HDF5FileMode::READWRITE,
HDF5FileMode::OVERWRITE}) {
SPDLOG_DEBUG("Constructing in mode {}", i);
HDF5File fi(tmp.string() + "/test_file_constructor.h5", static_cast<HDF5FileMode>(i));
CHECK(fi.isOK());
}// destructor called as we leave scope
}

SECTION("Check all reasonable file extensions are OK") {
for (auto extension : {".hdf5", ".h5", ".mat"}) {
HDF5File fi(tmp.string() + "/test_file" + extension, OVERWRITE);
CHECK(fi.isOK());
}
}

SECTION("Check can't open nonexistent file") {
CHECK_THROWS(HDF5File(tmp.string() + "/this_file_doesnt_exist.h5", HDF5FileMode::READONLY));
}

// We should get an exception if we try to write to a readonly file.
SECTION("Check can't open file readonly and write to it") {
// create a file
{
HDF5File f1(tmp.string() + "/test_file_read_only.h5", HDF5FileMode::OVERWRITE);
}// destructor called as we leave scope

HDF5File f2(tmp.string() + "/test_file_read_only.h5", HDF5FileMode::READONLY);
CHECK_THROWS(f2.write());
}

// Normal operation: we should be able to create a file and write to it, then read from it.
SECTION("Check write then read.") {
// create a file
{
HDF5File f1(tmp.string() + "/test_file_wr.h5", HDF5FileMode::OVERWRITE);
f1.write();
CHECK(f1.isOK());

}// destructor called as we leave scope

HDF5File f2(tmp.string() + "/test_file_wr.h5", HDF5FileMode::READONLY);
f2.read();
}

SECTION("Check write then (overwrite) then read.") {

// Create the file and write some data.
{
HDF5File f1(tmp.string() + "/test_file_wwr.h5", HDF5FileMode::OVERWRITE);
f1.write();
CHECK(f1.isOK());

}// destructor called as we leave scope

// Overwrite the file and add some different data.
{
HDF5File f2(tmp.string() + "/test_file_wwr.h5", HDF5FileMode::OVERWRITE);
f2.write();
CHECK(f2.isOK());

}// destructor called as we leave scope

// Now open the file in readonly. The first data should not be there (and
// should throw an exception). The second data should be there.
HDF5File f3(tmp.string() + "/test_file_wwr.h5", HDF5FileMode::READONLY);
f3.read();

CHECK_NOTHROW(f3.read());
CHECK_THROWS(f3.read());
}

// teardown - remove temporary directory and all files
SPDLOG_DEBUG("Removing temporary directory.");
std::filesystem::remove_all(tmp);
}

TEST_CASE("Test call example sandbox function") {
// setup - temporary directory
auto tmp = create_tmp_dir();

SPDLOG_INFO("Calling the example");
example_hdf5();
SPDLOG_INFO("Example has run");

// teardown - remove temporary directory and all files
SPDLOG_DEBUG("Removing temporary directory.");
std::filesystem::remove_all(tmp);
}

0 comments on commit 93f7fdf

Please sign in to comment.