diff --git a/src/communication/exporter_interface.hpp b/src/communication/exporter_interface.hpp index 8093ec3fb60b2fd406cf3d061864fa6b30ac57f0..a41357180063cc66539d36bf6711ef3ce869a490 100644 --- a/src/communication/exporter_interface.hpp +++ b/src/communication/exporter_interface.hpp @@ -3,36 +3,27 @@ #include <random> #include <string> -#include <spike.hpp> #include <common_types.hpp> +#include <spike.hpp> namespace nest { namespace mc { namespace communication { - -template <typename Time, typename CommunicationPolicy> // TODO: Templating on data type, for now only spike_type + +// interface for exporters. +// Exposes one virtual functions: +// do_export(vector<type>) receiving a vector of parameters to export +template <typename Time, typename CommunicationPolicy> // TODO: Templating on data type class exporter_interface { public: using time_type = Time; using spike_type = spike<cell_member_type, time_type>; - // Performs the export of the data, thread safe buffered + // Performs the export of the data virtual void do_export(const std::vector<spike_type>&) = 0; - - // Internal state is ok - // Export might encounter problems in a separate thread. - virtual bool ok() const = 0; - - // TODO: Enum with status strings (might be added to the implemenation) - // returns a textual explanation of the current error state - // - // virtual string status_description() = 0; - //virtual string status_id() = 0; }; - - } //communication } // namespace mc -} // namespace nest \ No newline at end of file +} // namespace nest diff --git a/src/communication/exporter_spike_file.hpp b/src/communication/exporter_spike_file.hpp index 41e83f16aac61c142955c14e2817bf77f4bf7981..5c0297cfa85d3c4bd779fc227913ad2e4fc0dd60 100644 --- a/src/communication/exporter_spike_file.hpp +++ b/src/communication/exporter_spike_file.hpp @@ -1,5 +1,6 @@ #pragma once +#include <cstring> #include <cstdio> #include <fstream> #include <iomanip> @@ -8,62 +9,59 @@ #include <stdexcept> #include <vector> -#include <cstring> - #include <common_types.hpp> -#include <util.hpp> +#include <communication/exporter_interface.hpp> #include <spike.hpp> - - -#include "exporter_interface.hpp" +#include <util.hpp> namespace nest { namespace mc { namespace communication { template <typename Time, typename CommunicationPolicy> // TODO: Templating on data type, for now only spike_type -class exporter_spike_file : public exporter_interface<Time, CommunicationPolicy> { - +class exporter_spike_file : public exporter_interface<Time, CommunicationPolicy> +{ public: using time_type = Time; using spike_type = spike<cell_member_type, time_type>; using communication_policy_type = CommunicationPolicy; - // + // Constructor + // over_write if true will overwrite the specified output file (default = true) + // output_path relative or absolute path + // file_name will be appended with "_x" with x the rank number + // file_extention a seperator will be added automatically exporter_spike_file(std::string file_name, std::string path, - std::string file_extention, bool over_write=true, - bool single_file_per_rank=true) + std::string file_extention, bool over_write=true) : - ok_(false) + file_path_(create_output_file_path( + file_name, path, file_extention, communication_policy_.id())) { - std::string file_path(create_output_file_path( - file_name, path, file_extention, communication_policy_.id())); - //test if the file exist and depending on over_write throw or delete - std::ifstream f(file_path); + std::ifstream f(file_path_); if (f.good()) { if (!over_write) { std::string error_string("Tried opening file for writing but it exists and over_write is false: " + - file_path); + file_path_); throw std::runtime_error(error_string); } - std::remove(file_path.c_str()); + std::remove(file_path_.c_str()); } buffer = new char[length]; - file_handle_ = nest::mc::util::make_unique<std::ofstream>(file_path, - std::fstream::app); - if (file_handle_->good()) { - ok_ = true; + file_handle_ = nest::mc::util::make_unique<std::ofstream>(file_path_, std::fstream::app); + + if (!file_handle_->good()) { + std::string error_string("Could not open file for writing: " + file_path_); + throw std::runtime_error(error_string); } } - - // Performs the export of the data, - // Does not throw + // Performs the a export of the spikes to file + // one id and spike time with 4 decimals after the comma on a line space separated void do_export(const std::vector<spike_type>& spikes) override { unsigned current_loc_in_buffer = 0; @@ -74,8 +72,10 @@ public: const char * space = " "; const char * endline = "\n"; - for (auto spike : spikes) - { + for (auto spike : spikes) { + // Manually convert the id and spike time to chars and use mem copy + // to insert these in the buffer. + // First the id as output nr_chars_written = std::snprintf(single_value_buffer, 20, "%u", spike.source.gid); @@ -83,7 +83,7 @@ public: nr_chars_written); current_loc_in_buffer += nr_chars_written; - // The a space + // Then a space std::memcpy(buffer + current_loc_in_buffer, space, 1); current_loc_in_buffer += 1; @@ -96,39 +96,34 @@ public: // Then the endline std::memcpy(buffer + current_loc_in_buffer, endline, 2); - current_loc_in_buffer += 1; // Only a single char in the actual file!! + // endl is only a single char in the actual file!! + // TODO: WINDOWS? or should we asume newline seperated as the interface + current_loc_in_buffer += 1; // Check if we are nearing the end of our buffer - if (current_loc_in_buffer > length - 45) - { + // maximum size of the inserted character in the loop is 2 * 20 + 3 + // So if there are less then 45 chars left in the buffer, write to + // file. and reset the buffer index to zero + if (current_loc_in_buffer > length - 45) { file_handle_->write(buffer, current_loc_in_buffer); current_loc_in_buffer = 0; } } - // also write to buffer at end of the spikes processing - if (current_loc_in_buffer != 0) - { + // write to buffer at end of the spikes processing + if (current_loc_in_buffer != 0) { file_handle_->write(buffer, current_loc_in_buffer); current_loc_in_buffer = 0; // not needed } - file_handle_->flush(); - if (!file_handle_->good()){ - ok_ = false; - } + std::string error_string("Error writing data file: " + + file_path_); - } - - // Internal state is ok - // We are working with fstreams possibly on a seperate thread - // We need a way to assertain the status of the stream - bool ok() const override - { - return ok_ && file_handle_->good(); + throw std::runtime_error(error_string); + } } // Creates an indexed filename @@ -137,17 +132,17 @@ public: { std::string file_path = path + file_name + "_" + std::to_string(index) + "." + file_extention; - // TODO: Nest does not produce the indexing for nrank == 0 - // I have the feeling this disrupts consistent output. Id rather - // always put the zero in. + // Nest does not produce the indexing for nrank == 0 + // I have the feeling this disrupts consistent output. Id rather + // always put the zero in. it allows a simpler regex when opening + // files return file_path; } private: - // Are we in a valid state? - bool ok_; + std::string file_path_; - // Handle to our owned opened file handle + // Handle to opened file handle std::unique_ptr<std::ofstream> file_handle_; communication_policy_type communication_policy_; diff --git a/tests/global_communication/test_exporter_spike_file.cpp b/tests/global_communication/test_exporter_spike_file.cpp index 6201c73103dda2bcce10e68c638798ac78bab384..b7d9b893919db071b9e2e936f3c51db01dd7e3ef 100644 --- a/tests/global_communication/test_exporter_spike_file.cpp +++ b/tests/global_communication/test_exporter_spike_file.cpp @@ -10,8 +10,8 @@ #include <communication/global_policy.hpp> #include <communication/exporter_spike_file.hpp> -class exporter_spike_file_fixture : public ::testing::Test { - +class exporter_spike_file_fixture : public ::testing::Test +{ protected: using time_type = float; using communicator_type = nest::mc::communication::global_policy; @@ -39,34 +39,46 @@ protected: file_name, path, extention, 0); } - void SetUp() { + void SetUp() + { // code here will execute just before the test ensues } - void TearDown() { + void TearDown() + { // delete the start create file std::remove(get_standard_file_name().c_str()); } - ~exporter_spike_file_fixture() { + ~exporter_spike_file_fixture() + { } }; TEST_F(exporter_spike_file_fixture, constructor) { - exporter_type exporter(file_name, path, extention, true); - // after construction the state of the exporter should be valid - EXPECT_TRUE(exporter.ok()); - - //test if the file exist and depending on over_write throw or delete std::ifstream f(get_standard_file_name()); - EXPECT_TRUE(f.good()); + + // We now know the file exists, so create a new exporter with overwrite false + try + { + exporter_type exporter1(file_name, path, extention, false); + FAIL() << "expected a file already exists error"; + } + catch (std::runtime_error const & err) + { + EXPECT_EQ(err.what(), std::string("Tried opening file for writing but it exists and over_write is false: " + + get_standard_file_name())); + } + catch (...) { + FAIL() << "expected a file already exists error"; + } } TEST_F(exporter_spike_file_fixture, create_output_file_path) @@ -85,10 +97,7 @@ TEST_F(exporter_spike_file_fixture, create_output_file_path) TEST_F(exporter_spike_file_fixture, do_export) { - - - exporter_type exporter(file_name, - path, extention); + exporter_type exporter(file_name, path, extention); // Create some spikes std::vector<spike_type> spikes; @@ -97,11 +106,10 @@ TEST_F(exporter_spike_file_fixture, do_export) spikes.push_back({ { 1, 0 }, 1.0 }); spikes.push_back({ { 1, 0 }, 1.1 }); - // now do the export exporter.do_export(spikes); - // Test if we have spikes in the file + // Test if we have spikes in the file? std::ifstream f(get_standard_file_name()); EXPECT_TRUE(f.good());