diff --git a/.gitignore b/.gitignore index c1833bba173b6133b0970eb5dcd6cae4a4c69791..ff2696ed75fe48bd54e9d654d2c139a2d9fa1c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ *.swo *.swn *.swq +*.swm +*.swl # tar files *.tar diff --git a/CMakeLists.txt b/CMakeLists.txt index 041c025c3bd1725bcecda9ebb02d959ff19cff07..8da46487de7eb56194909cbcb81a1262ff2a6444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,33 @@ endif() # whether to generate optimized kernels from NMODL set(USE_OPTIMIZED_KERNELS OFF CACHE BOOL "generate optimized code that vectorizes with the Intel compiler") +# Only build modcc if it has not already been installed. +# This is useful if cross compiling for KNL, when it is not desirable to compile +# modcc with the same flags that are used for the KNL target. +find_program(MODCC_BIN modcc) +set(modcc "${MODCC_BIN}") +set(use_external_modcc OFF BOOL) + +# the modcc executable was not found, so build our own copy +if(MODCC_BIN STREQUAL "MODCC_BIN-NOTFOUND") + include(ExternalProject) + externalproject_add(modparser + PREFIX ${CMAKE_BINARY_DIR}/external + CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external" + "-DCMAKE_CXX_FLAGS=${SAVED_CXX_FLAGS}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + BINARY_DIR "${CMAKE_BINARY_DIR}/external/modparser" + STAMP_DIR "${CMAKE_BINARY_DIR}/external/" + TMP_DIR "${CMAKE_BINARY_DIR}/external/tmp" + SOURCE_DIR "${CMAKE_SOURCE_DIR}/external/modparser" + ) + # Set up environment to use the version of modcc that is compiled + # as the ExternalProject above. + set(use_external_modcc ON) + set(modcc "${CMAKE_BINARY_DIR}/external/bin/modcc") +endif() + + include_directories(${CMAKE_SOURCE_DIR}/external) include_directories(${CMAKE_SOURCE_DIR}/external/tclap/include) include_directories(${CMAKE_SOURCE_DIR}/include) diff --git a/mechanisms/CMakeLists.txt b/mechanisms/CMakeLists.txt index ace3ccda304be110d1cc65b215986759c88647b3..80925e24320fe3a01d0c2eee24e5494e58ecd14a 100644 --- a/mechanisms/CMakeLists.txt +++ b/mechanisms/CMakeLists.txt @@ -1,30 +1,3 @@ - -# Only build modcc if it has not already been installed. -# This is useful if cross compiling for KNL, when it is not desirable to compile -# modcc with the same flags that are used for the KNL target. -find_program(MODCC_BIN modcc) -set(modcc "${MODCC_BIN}") -set(use_external_modcc OFF BOOL) - -# the modcc executable was not found, so build our own copy -if(MODCC_BIN STREQUAL "MODCC_BIN-NOTFOUND") - include(ExternalProject) - externalproject_add(modparser - PREFIX ${CMAKE_BINARY_DIR}/external - CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external" - "-DCMAKE_CXX_FLAGS=${SAVED_CXX_FLAGS}" - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - BINARY_DIR "${CMAKE_BINARY_DIR}/external/modparser" - STAMP_DIR "${CMAKE_BINARY_DIR}/external/" - TMP_DIR "${CMAKE_BINARY_DIR}/external/tmp" - SOURCE_DIR "${CMAKE_SOURCE_DIR}/external/modparser" - ) - # Set up environment to use the version of modcc that is compiled - # as the ExternalProject above. - set(use_external_modcc ON) - set(modcc "${CMAKE_BINARY_DIR}/external/bin/modcc") -endif() - # the list of built-in mechanisms to be provided by default set(mechanisms pas hh expsyn exp2syn) diff --git a/src/model.hpp b/src/model.hpp index 9eacbbc93677e3f2453558bde390427650a73986..41fb391ca614f60c01e37b170ed0774fd03b0cbc 100644 --- a/src/model.hpp +++ b/src/model.hpp @@ -242,7 +242,6 @@ private: using event_queue_type = typename communicator_type::event_queue; util::double_buffer< std::vector<event_queue_type> > event_queues_; - using spike_type = typename communicator_type::spike_type; using local_spike_store_type = thread_private_spike_store<time_type>; util::double_buffer< local_spike_store_type > local_spikes_; diff --git a/tests/global_communication/CMakeLists.txt b/tests/global_communication/CMakeLists.txt index fc8531dac8561fa748479af8d7633e9339c9a4f4..cc1e1af8b6567a2fc887815fa4eaeb25c561841e 100644 --- a/tests/global_communication/CMakeLists.txt +++ b/tests/global_communication/CMakeLists.txt @@ -3,6 +3,8 @@ set(HEADERS ) set(COMMUNICATION_SOURCES test_exporter_spike_file.cpp + test_communicator.cpp + # unit test driver test.cpp ) diff --git a/tests/global_communication/mpi_listener.hpp b/tests/global_communication/mpi_listener.hpp new file mode 100644 index 0000000000000000000000000000000000000000..968424c3e55f5da1dc81022ef75cf0de176a82fd --- /dev/null +++ b/tests/global_communication/mpi_listener.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include <cstdio> +#include <ostream> +#include <stdexcept> + +#include <communication/global_policy.hpp> + +#include "gtest.h" + +/// A specialized listener desinged for printing test results with MPI. +/// +/// When tests are run with MPI, one instance of each test is run on +/// each rank. The default behavior of Google Test is for each test +/// instance to print to stdout. With more than one MPI rank, this creates +/// the usual MPI mess of output. +/// +/// This specialization has the first rank (rank 0) print to stdout, and all MPI +/// ranks print their output to separate text files. +/// For each test a message is printed showing +/// - detailed messages about errors on rank 0 +/// - a head count of errors that occured on other MPI ranks + +class mpi_listener : public testing::EmptyTestEventListener { +private: + using UnitTest = testing::UnitTest; + using TestCase = testing::TestCase; + using TestInfo = testing::TestInfo; + using TestPartResult = testing::TestPartResult; + + int rank_; + int size_; + std::ofstream fid_; + char buffer_[1024]; + bool test_case_failures_; + bool test_case_tests_; + int test_failures_; + + bool does_print() const { + return rank_==0; + } + + void print(const char* s) { + fid_ << s; + if (does_print()) { + std::cout << s; + } + } + + void print(const std::string& s) { + print(s.c_str()); + } + + template <typename... Args> + void pprintf(const char* s, Args&&... args) { + snprintf(buffer_, sizeof(buffer_), s, std::forward<Args>(args)...); + print(buffer_); + } + +public: + + mpi_listener(std::string f_base) { + rank_ = nest::mc::communication::global_policy::id(); + size_ = nest::mc::communication::global_policy::size(); + + std::string fname = f_base + "_" + std::to_string(rank_) + ".txt"; + fid_.open(fname); + if (!fid_.good()) { + throw std::runtime_error("could not open file " + fname + " for test output"); + } + } + + /// Messages that are printed at the start and end of the test program. + /// i.e. once only. + virtual void OnTestProgramStart(const UnitTest&) override { + pprintf("*** test output for rank %d of %d\n\n", rank_, size_); + } + virtual void OnTestProgramEnd(const UnitTest&) override { + pprintf("*** end test output for rank %d of %d\n", rank_, size_); + } + + /// Messages that are printed at the start and end of each test case. + /// On startup a counter that counts the number of tests that fail in + /// this test case is initialized to zero, and will be incremented for each + /// test that fails. + virtual void OnTestCaseStart(const TestCase& test_case) override { + test_case_failures_ = 0; + test_case_tests_ = 0; + } + virtual void OnTestCaseEnd(const TestCase& test_case) override { + pprintf( + "TESTCASE %s : %2d:%-2d pass:fail of %2d tests\n\n", + test_case.name(), + test_case_failures_, test_case_tests_-test_case_failures_, test_case_tests_ + ); + } + + // Called before a test starts. + virtual void OnTestStart(const TestInfo& test_info) override { + pprintf( " TEST %s::%s\n", test_info.test_case_name(), test_info.name()); + test_failures_ = 0; + } + + // Called after a failed assertion or a SUCCEED() invocation. + virtual void OnTestPartResult(const TestPartResult& test_part_result) override { + const char* banner = "--------------------------------------------------------------------------------"; + + // indent all lines in the summary by 4 spaces + std::string summary = " " + std::string(test_part_result.summary()); + auto pos = summary.find("\n"); + while (pos!=summary.size() && pos!=std::string::npos) { + summary.replace(pos, 1, "\n "); + pos = summary.find("\n", pos+1); + } + + pprintf( + " LOCAL_%s\n %s\n %s:%d\n%s\n %s\n", + test_part_result.failed() ? "FAIL" : "SUCCESS", + banner, + test_part_result.file_name(), + test_part_result.line_number(), + summary.c_str(), + banner + ); + + // note that there was a failure in this test case + if (test_part_result.failed()) { + test_failures_++; + } + } + + // Called after a test ends. + virtual void OnTestEnd(const TestInfo& test_info) override { + test_case_tests_++; + + // count the number of ranks that had errors + int global_errors = + nest::mc::communication::global_policy::sum(test_failures_>0 ? 1 : 0); + if (global_errors>0) { + test_case_failures_++; + pprintf(" GLOBAL_FAIL on %d ranks\n", global_errors); + } + } +}; + diff --git a/tests/global_communication/test.cpp b/tests/global_communication/test.cpp index c67a065bd88ed85aa56e865b1d4f8f9fa3d5677e..640e4006973db9e65251e55b76132bbc8b908eea 100644 --- a/tests/global_communication/test.cpp +++ b/tests/global_communication/test.cpp @@ -5,15 +5,35 @@ #include "gtest.h" -#include "../../src/communication/communicator.hpp" -#include "../../src/communication/global_policy.hpp" +#include "mpi_listener.hpp" -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <util/ioutil.hpp> +using namespace nest; + +int main(int argc, char **argv) { // We need to set the communicator policy at the top level // this allows us to build multiple communicators in the tests - nest::mc::communication::global_policy_guard global_guard(argc, argv); + mc::communication::global_policy_guard global_guard(argc, argv); + + // initialize google test environment + ::testing::InitGoogleTest(&argc, argv); + + // set up a custom listener that prints messages in an MPI-friendly way + auto& listeners = testing::UnitTest::GetInstance()->listeners(); + // first delete the original printer + delete listeners.Release(listeners.default_result_printer()); + // now add our custom printer + listeners.Append(new mpi_listener("global_comms")); + + // record the local return value for tests run on this mpi rank + // 0 : success + // 1 : failure + auto result = RUN_ALL_TESTS(); - return RUN_ALL_TESTS(); + // perform global collective, to ensure that all ranks return + // the same exit code + return mc::communication::global_policy::max(result); } diff --git a/tests/global_communication/test_communicator.cpp b/tests/global_communication/test_communicator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1b50e1b037e70146272e8f3c2737ef45c50505b9 --- /dev/null +++ b/tests/global_communication/test_communicator.cpp @@ -0,0 +1,18 @@ +#include "gtest.h" + +#include <cstdio> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> + +using namespace nest::mc; + +using time_type = float; +using communicator_type = communication::communicator<time_type, communication::global_policy>; + +TEST(communicator, setup) { +}