diff --git a/libmuscle/cpp/build/libmuscle/Makefile b/libmuscle/cpp/build/libmuscle/Makefile index b638d1b0..e6cb997a 100644 --- a/libmuscle/cpp/build/libmuscle/Makefile +++ b/libmuscle/cpp/build/libmuscle/Makefile @@ -57,7 +57,7 @@ public_headers := libmuscle/data.hpp libmuscle/data.tpp libmuscle/instance.hpp public_headers += libmuscle/libmuscle.hpp libmuscle/mcp/data_pack.hpp public_headers += libmuscle/mcp/data_pack.tpp libmuscle/message.hpp public_headers += libmuscle/ports_description.hpp libmuscle/util.hpp libmuscle/util.tpp -public_headers += libmuscle/version.h libmuscle/namespace.hpp +public_headers += libmuscle/version.h libmuscle/namespace.hpp libmuscle/test_support.hpp installed_headers := $(public_headers:%=$(PREFIX)/include/%) pkg_config_files := libmuscle.pc diff --git a/libmuscle/cpp/src/libmuscle/tests/fixtures.hpp b/libmuscle/cpp/src/libmuscle/tests/fixtures.hpp new file mode 100644 index 00000000..217f1d42 --- /dev/null +++ b/libmuscle/cpp/src/libmuscle/tests/fixtures.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +// Note: using POSIX for filesystem calls +// Could be upgraded to std::filesystem when targeting C++17 or later +#include +#include +#include +#include +#include +#include +#include + + +// callback for nftw() to delete all contents of a folder +int _nftw_rm_callback( + const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf) { + if (tflag == FTW_DP) { + std::cerr << "DEBUG: removing dir " << fpath << std::endl; + return rmdir(fpath); + } + if (tflag == FTW_F) { + std::cerr << "DEBUG: removing file " << fpath << std::endl; + return unlink(fpath); + } + std::cerr << "DEBUG: unknown file type " << fpath << std::endl; + return -1; +} + +struct TempDirFixture { + TempDirFixture() { + char tmpname[] = "/tmp/muscle3_test.XXXXXX"; + if (mkdtemp(tmpname) == nullptr) { + throw std::runtime_error(strerror(errno)); + } + temp_dir_ = tmpname; + std::cerr << "DEBUG: using temp dir " << temp_dir_ << std::endl; + } + + ~TempDirFixture() { + // simulate rm -rf `temp_dir_` using a file-tree-walk + if (nftw(temp_dir_.c_str(), _nftw_rm_callback, 3, FTW_DEPTH) < 0) { + std::cerr << "ERROR: Could not remove temp dir at " << temp_dir_ << std::endl; + std::cerr << "ERROR: " << strerror(errno) << std::endl; + } + } + + std::string temp_dir_; +}; + diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.cpp deleted file mode 100644 index 6448329e..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -namespace mcp { - -MockTcpTransportServer::MockTcpTransportServer(RequestHandler & handler) - : TransportServer(handler) -{ - ++num_constructed; -} - -MockTcpTransportServer::~MockTcpTransportServer() {} - -std::string MockTcpTransportServer::get_location() const { - return "tcp:test_location"; -} - -void MockTcpTransportServer::close() {} - - -void MockTcpTransportServer::reset() { - num_constructed = 0; -} - -int MockTcpTransportServer::num_constructed = 0; - -} - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.hpp index d28b1f6f..b67c8e11 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mcp/mock_tcp_transport_server.hpp @@ -8,20 +8,35 @@ namespace libmuscle { namespace _MUSCLE_IMPL_NS { namespace mcp { -class MockTcpTransportServer : public TransportServer { +class MockTcpTransportServer + : public MockClass + , public TransportServer +{ public: - MockTcpTransportServer(RequestHandler & handler); + MockTcpTransportServer(ReturnValue) { + NAME_MOCK_MEM_FUN(MockTcpTransportServer, constructor); + NAME_MOCK_MEM_FUN(MockTcpTransportServer, get_location_mock); + NAME_MOCK_MEM_FUN(MockTcpTransportServer, close_mock); + } - ~MockTcpTransportServer(); + MockTcpTransportServer(RequestHandler & handler) { + init_from_return_value(); + constructor(handler); + } - virtual std::string get_location() const; + MockFun> constructor; - virtual void close(); + virtual std::string get_location() const override { + return get_location_mock(); + } - // Mock control variables - static void reset(); + mutable MockFun> get_location_mock; - static int num_constructed; + virtual void close() override { + return close_mock(); + } + + MockFun close_mock; }; using TcpTransportServer = MockTcpTransportServer; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.cpp deleted file mode 100644 index 4f287ee5..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include - -#include -#include - -#include - -using libmuscle::_MUSCLE_IMPL_NS::Data; -using libmuscle::_MUSCLE_IMPL_NS::DataConstRef; -using libmuscle::_MUSCLE_IMPL_NS::Port; - -using ymmsl::Conduit; -using ymmsl::Identifier; -using ymmsl::Operator; -using ymmsl::Reference; -using ymmsl::Settings; - - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -MockCommunicator::MockCommunicator( - ymmsl::Reference const & kernel, - std::vector const & index, - Optional const & declared_ports, - Logger & logger, Profiler & profiler) -{ - ++num_constructed; -} - -std::vector MockCommunicator::get_locations() const { - return std::vector({"tcp:test1,test2", "tcp:test3"}); -} - -void MockCommunicator::connect( - std::vector const & conduits, - PeerDims const & peer_dims, - PeerLocations const & peer_locations) -{ -} - -bool MockCommunicator::settings_in_connected() const { - return settings_in_connected_return_value; -} - -PortsDescription MockCommunicator::list_ports() const { - return list_ports_return_value; -} - -bool MockCommunicator::port_exists(std::string const & port_name) const { - return port_exists_return_value; -} - -Port const & MockCommunicator::get_port(std::string const & port_name) const { - return get_port_return_value.at(port_name); -} - -Port & MockCommunicator::get_port(std::string const & port_name) { - return get_port_return_value.at(port_name); -} - -void MockCommunicator::send_message( - std::string const & port_name, - Message const & message, - Optional slot) -{ - last_sent_port = port_name; - last_sent_message = message; - last_sent_slot = slot; -} - -Message MockCommunicator::receive_message( - std::string const & port_name, - Optional slot, - Optional const & default_msg) -{ - Reference key(port_name); - if (slot.is_set()) - key += slot.get(); - if (next_received_message.count(key) == 0) { - assert(default_msg.is_set()); - return default_msg.get(); - } - if (is_close_port(next_received_message.at(key)->data())) { - if (slot.is_set()) - get_port_return_value.at(port_name).set_closed(slot.get()); - else - get_port_return_value.at(port_name).set_closed(); - } - - return *next_received_message.at(key); -} - -void MockCommunicator::close_port( - std::string const & port_name, Optional slot) -{ -} - -void MockCommunicator::shutdown() { -} - - -MockCommunicator::PortMessageCounts MockCommunicator::get_message_counts() { - return get_message_counts_return_value; -} - -void MockCommunicator::restore_message_counts( - PortMessageCounts const & port_message_counts){ - last_restored_message_counts = port_message_counts; -} - - -void MockCommunicator::reset() { - num_constructed = 0; - settings_in_connected_return_value = false; - port_exists_return_value = true; - get_port_return_value.clear(); - next_received_message.clear(); - list_ports_return_value.clear(); - last_sent_port = ""; - last_sent_message = Message(0.0); - last_sent_slot = {}; - get_message_counts_return_value.clear(); - last_restored_message_counts = {}; -} - -int MockCommunicator::num_constructed = 0; - -bool MockCommunicator::settings_in_connected_return_value = false; - -bool MockCommunicator::port_exists_return_value = true; - -std::unordered_map MockCommunicator::get_port_return_value; - -std::unordered_map> - MockCommunicator::next_received_message; - -PortsDescription MockCommunicator::list_ports_return_value; - -std::string MockCommunicator::last_sent_port; - -Message MockCommunicator::last_sent_message(0.0); - -Optional MockCommunicator::last_sent_slot; - -MockCommunicator::PortMessageCounts MockCommunicator::get_message_counts_return_value; - -MockCommunicator::PortMessageCounts MockCommunicator::last_restored_message_counts; - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.hpp index 2e2f7c28..4c2f29a3 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_communicator.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -17,70 +16,128 @@ #include +namespace mock_communicator { + +using ::libmuscle::_MUSCLE_IMPL_NS::Message; +using ::libmuscle::_MUSCLE_IMPL_NS::Optional; + + +using CommunicatorSendMessageBase = MockFun< + Void, Val, Val, + Val>>; + +struct CommunicatorSendMessageMock : CommunicatorSendMessageBase { + void operator()( + std::string const & port_name, + Message const & message, + Optional slot = {}) { + CommunicatorSendMessageBase::operator()(port_name, message, slot); + } +}; + + +using CommunicatorReceiveMessageBase = MockFun< + Val, Val, + Val>, + Val>>; + +struct CommunicatorReceiveMessageMock : CommunicatorReceiveMessageBase { + Message operator()( + std::string const & port_name, + Optional slot = {}, + Optional const & default_msg = {}) + { + return CommunicatorReceiveMessageBase::operator()( + port_name, slot, default_msg); + } +}; + + +using CommunicatorClosePortBase = MockFun< + Void, Val, Val>>; + +struct CommunicatorClosePortMock : CommunicatorClosePortBase { + void operator()(std::string const & port_name, Optional slot = {}) { + CommunicatorClosePortBase::operator()(port_name, slot); + } +}; + +} + + namespace libmuscle { namespace _MUSCLE_IMPL_NS { using PortsDescription = std::unordered_map>; -class MockCommunicator { +class MockCommunicator : public MockClass { public: using PortMessageCounts = std::unordered_map>; + MockCommunicator(ReturnValue) { + NAME_MOCK_MEM_FUN(MockCommunicator, constructor); + NAME_MOCK_MEM_FUN(MockCommunicator, get_locations); + NAME_MOCK_MEM_FUN(MockCommunicator, connect); + NAME_MOCK_MEM_FUN(MockCommunicator, settings_in_connected); + NAME_MOCK_MEM_FUN(MockCommunicator, list_ports); + NAME_MOCK_MEM_FUN(MockCommunicator, port_exists); + NAME_MOCK_MEM_FUN(MockCommunicator, get_port); + NAME_MOCK_MEM_FUN(MockCommunicator, send_message); + NAME_MOCK_MEM_FUN(MockCommunicator, receive_message); + NAME_MOCK_MEM_FUN(MockCommunicator, close_port); + NAME_MOCK_MEM_FUN(MockCommunicator, shutdown); + NAME_MOCK_MEM_FUN(MockCommunicator, get_message_counts); + NAME_MOCK_MEM_FUN(MockCommunicator, restore_message_counts); + + get_locations.return_value = std::vector( + {"tcp:test1,test2", "tcp:test3"}); + list_ports.return_value = PortsDescription(); + } + + MockCommunicator() { + init_from_return_value(); + } + MockCommunicator( ymmsl::Reference const & kernel, std::vector const & index, Optional const & declared_ports, - Logger & logger, Profiler & profiler); - - std::vector get_locations() const; + Logger & logger, Profiler & profiler) + { + init_from_return_value(); + constructor(kernel, index, declared_ports, logger, profiler); + } - void connect( - std::vector const & conduits, - PeerDims const & peer_dims, - PeerLocations const & peer_locations); + MockFun< + Void, Val, Val const &>, + Val const &>, Obj, Obj> + constructor; - bool settings_in_connected() const; + MockFun>> get_locations; - PortsDescription list_ports() const; + MockFun< + Void, Val const &>, Val, + Val> connect; - bool port_exists(std::string const & port_name) const; + MockFun> settings_in_connected; - Port const & get_port(std::string const & port_name) const; + MockFun> list_ports; - Port & get_port(std::string const & port_name); + MockFun, Val> port_exists; - void send_message( - std::string const & port_name, - Message const & message, - Optional slot = {}); + MockFun, Val> get_port; - Message receive_message( - std::string const & port_name, - Optional slot = {}, - Optional const & default_msg = {} - ); + ::mock_communicator::CommunicatorSendMessageMock send_message; - void close_port(std::string const & port_name, Optional slot = {}); + ::mock_communicator::CommunicatorReceiveMessageMock receive_message; - void shutdown(); + ::mock_communicator::CommunicatorClosePortMock close_port; - PortMessageCounts get_message_counts(); + MockFun shutdown; - void restore_message_counts(PortMessageCounts const & port_message_counts); + MockFun> get_message_counts; - static void reset(); - static int num_constructed; - static bool settings_in_connected_return_value; - static bool port_exists_return_value; - static std::unordered_map get_port_return_value; - static std::unordered_map> - next_received_message; - static PortsDescription list_ports_return_value; - static std::string last_sent_port; - static Message last_sent_message; - static Optional last_sent_slot; - static PortMessageCounts get_message_counts_return_value; - static PortMessageCounts last_restored_message_counts; + MockFun> restore_message_counts; }; using Communicator = MockCommunicator; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.cpp deleted file mode 100644 index 797c1bd0..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "mocks/mock_logger.hpp" - - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -MockLogger::MockLogger() {} - -MockLogger::MockLogger( - std::string const & instance_id, - std::string const & log_file, MMPClient & manager) {} - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.hpp index d4be69b5..6ff5bfca 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_logger.hpp @@ -4,37 +4,97 @@ #include #include +#include + +#include + + +template +void concat_to_stringstream(std::ostringstream & os, Args... args); + +template <> +void concat_to_stringstream(std::ostringstream & os) {} + +template +void concat_to_stringstream(std::ostringstream & os, T t, Args... args) { + os << t; + concat_to_stringstream(os, args...); +} + namespace libmuscle { namespace _MUSCLE_IMPL_NS { -class MockLogger { +class MockLogger : public MockClass { public: - MockLogger(); + MockLogger(ReturnValue) { + NAME_MOCK_MEM_FUN(MockLogger, constructor); + NAME_MOCK_MEM_FUN(MockLogger, set_remote_level); + NAME_MOCK_MEM_FUN(MockLogger, set_local_level); + NAME_MOCK_MEM_FUN(MockLogger, caplog); + } + + MockLogger() { + init_from_return_value(); + } + MockLogger( - std::string const & instance_id, - std::string const & log_file, MMPClient & manager); + std::string const & instance_id, std::string const & log_file, + MMPClient & manager) + { + init_from_return_value(); + constructor(instance_id, log_file, manager); + } - void set_remote_level(LogLevel level) {} + MockFun< + Void, Val, Val, + Obj + > constructor; + MockFun> set_remote_level; + MockFun> set_local_level; - void set_local_level(LogLevel level) {} + MockFun, Val> caplog; template - void log(LogLevel level, Args... args) {} + void log(LogLevel && level, Args... args) { + std::ostringstream s; + concat_to_stringstream(s, args...); + caplog(std::forward(level), s.str()); + } template - void critical(Args... args) {} + void critical(Args... args) { + std::ostringstream s; + concat_to_stringstream(s, args...); + caplog(LogLevel::CRITICAL, s.str()); + } template - void error(Args... args) {} + void error(Args... args) { + std::ostringstream s; + concat_to_stringstream(s, args...); + caplog(LogLevel::ERROR, s.str()); + } template - void warning(Args... args) {} + void warning(Args... args) { + std::ostringstream s; + concat_to_stringstream(s, args...); + caplog(LogLevel::WARNING, s.str()); + } template - void info(Args... args) {} + void info(Args... args) { + std::ostringstream s; + concat_to_stringstream(s, args...); + caplog(LogLevel::INFO, s.str()); + } template - void debug(Args... args) {} + void debug(Args... args) { + std::ostringstream s; + concat_to_stringstream(s, args...); + caplog(LogLevel::DEBUG, s.str()); + } }; using Logger = MockLogger; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.cpp deleted file mode 100644 index 0fa14ee8..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "mocks/mock_mmp_client.hpp" - -#include - - -using ymmsl::Conduit; -using ymmsl::Reference; - - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -MockMMPClient::MockMMPClient( - Reference const & instance_id, std::string const & location) -{ - ++num_constructed; - last_instance_id = instance_id; - last_location = location; -} - -void MockMMPClient::close() {} - -void MockMMPClient::submit_log_message(LogMessage const & message) { - last_submitted_log_message = message; -} - -void MockMMPClient::submit_profile_events(std::vector const & event) { - last_submitted_profile_events = event; -} - -void MockMMPClient::submit_snapshot_metadata( - SnapshotMetadata const & snapshot_metadata) -{ - last_submitted_snapshot_metadata = snapshot_metadata; -} - -void MockMMPClient::register_instance( - std::vector const & locations, - std::vector<::ymmsl::Port> const & ports) -{ - last_registered_locations = locations; - last_registered_ports = ports; -} - -ymmsl::Settings MockMMPClient::get_settings() { - ymmsl::Settings settings; - settings["test_int"] = 10; - settings["test_string"] = "testing"; - return settings; -} - -auto MockMMPClient::get_checkpoint_info() -> - std::tuple< - double, - DataConstRef, - Optional, - Optional - > -{ - return { - 0.1, - // no checkpoints defined: - Data::dict( - "at_end", false, - "wallclock_time", Data::list(), - "simulation_time", Data::list()), - {}, - {}}; -} - -auto MockMMPClient::request_peers() -> - std::tuple< - std::vector<::ymmsl::Conduit>, - std::unordered_map<::ymmsl::Reference, std::vector>, - std::unordered_map<::ymmsl::Reference, std::vector> - > -{ - std::vector conduits; - - std::unordered_map> peer_dimensions; - - std::unordered_map> peer_locations; - - return std::make_tuple( - std::move(conduits), - std::move(peer_dimensions), - std::move(peer_locations)); -} - -void MockMMPClient::deregister_instance() {} - -void MockMMPClient::reset() { - num_constructed = 0; - last_instance_id = Reference("NONE"); - last_location = ""; - last_registered_locations.clear(); - last_registered_ports.clear(); - last_submitted_log_message.instance_id = ""; - last_submitted_log_message.timestamp = Timestamp(-1.0); - last_submitted_log_message.level = LogLevel::DEBUG; - last_submitted_log_message.text = ""; -} - -::ymmsl::Reference MockMMPClient::last_instance_id("NONE"); - -int MockMMPClient::num_constructed = 0; - -std::string MockMMPClient::last_location(""); - -std::vector MockMMPClient::last_registered_locations({}); - -std::vector<::ymmsl::Port> MockMMPClient::last_registered_ports({}); - -LogMessage MockMMPClient::last_submitted_log_message( - "", Timestamp(-1.0), LogLevel::DEBUG, ""); - -std::vector MockMMPClient::last_submitted_profile_events; - -Optional MockMMPClient::last_submitted_snapshot_metadata; - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.hpp index e0fa4eb4..2109eed0 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mmp_client.hpp @@ -14,55 +14,90 @@ #include +#include + namespace libmuscle { namespace _MUSCLE_IMPL_NS { -class MockMMPClient { +class MockMMPClient : public MockClass { public: - explicit MockMMPClient( - ymmsl::Reference const & instance_id, std::string const & location); - - void close(); - - void submit_log_message(LogMessage const & message); - - void submit_profile_events(std::vector const & event); - - void submit_snapshot_metadata(SnapshotMetadata const & snapshot_metadata); - - ymmsl::Settings get_settings(); - - auto get_checkpoint_info() -> - std::tuple< - double, - DataConstRef, - Optional, - Optional - >; - - void register_instance( - std::vector const & locations, - std::vector<::ymmsl::Port> const & ports); - - auto request_peers() -> - std::tuple< - std::vector<::ymmsl::Conduit>, - std::unordered_map<::ymmsl::Reference, std::vector>, - std::unordered_map<::ymmsl::Reference, std::vector> - >; - - void deregister_instance(); - - static void reset(); - - static ::ymmsl::Reference last_instance_id; - static int num_constructed; - static std::string last_location; - static std::vector last_registered_locations; - static std::vector<::ymmsl::Port> last_registered_ports; - static LogMessage last_submitted_log_message; - static std::vector last_submitted_profile_events; - static Optional last_submitted_snapshot_metadata; + MockMMPClient(ReturnValue) { + NAME_MOCK_MEM_FUN(MockMMPClient, constructor); + NAME_MOCK_MEM_FUN(MockMMPClient, close); + NAME_MOCK_MEM_FUN(MockMMPClient, submit_log_message); + NAME_MOCK_MEM_FUN(MockMMPClient, submit_profile_events); + NAME_MOCK_MEM_FUN(MockMMPClient, submit_snapshot_metadata); + NAME_MOCK_MEM_FUN(MockMMPClient, get_settings); + NAME_MOCK_MEM_FUN(MockMMPClient, get_checkpoint_info); + NAME_MOCK_MEM_FUN(MockMMPClient, register_instance); + NAME_MOCK_MEM_FUN(MockMMPClient, request_peers); + NAME_MOCK_MEM_FUN(MockMMPClient, deregister_instance); + + // default return value for request_peers + std::vector<::ymmsl::Conduit> conduits; + std::unordered_map<::ymmsl::Reference, std::vector> peer_dimensions; + std::unordered_map<::ymmsl::Reference, std::vector> peer_locations; + request_peers.return_value = std::make_tuple( + std::move(conduits), + std::move(peer_dimensions), + std::move(peer_locations)); + + // default return value for get_settings + ymmsl::Settings settings; + settings["test_int"] = 10; + settings["test_string"] = "testing"; + get_settings.return_value = settings; + + get_checkpoint_info.return_value = std::make_tuple( + 0.1, + // no checkpoints defined: + Data::dict( + "at_end", false, + "wallclock_time", Data::list(), + "simulation_time", Data::list()), + Optional(), + Optional()); + } + + MockMMPClient() { + init_from_return_value(); + } + + MockMMPClient( + ymmsl::Reference const & instance_id, std::string const & location) + { + init_from_return_value(); + constructor(instance_id, location); + } + + MockFun< + Void, Val, Val> constructor; + MockFun close; + MockFun> submit_log_message; + MockFun const &>> submit_profile_events; + MockFun> submit_snapshot_metadata; + MockFun> get_settings; + + // We use Data here instead of DataConstRef because DCR isn't assignable, + // not even move-assignable, and so we wouldn't be able to set the return + // value, and MockFun isn't smart enough to recursively traverse the tuple + // and replace the type automatically. + MockFun, Optional + >>> get_checkpoint_info; + + MockFun< + Void, Val const &>, + Val const &> + > register_instance; + + MockFun, + std::unordered_map<::ymmsl::Reference, std::vector>, + std::unordered_map<::ymmsl::Reference, std::vector> + >>> request_peers; + + MockFun deregister_instance; }; using MMPClient = MockMMPClient; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.cpp deleted file mode 100644 index f508ddd5..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include - - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -MockMPPClient::MockMPPClient(std::vector const & locations) { - ++num_constructed; -} - -MockMPPClient::~MockMPPClient() {} - -std::tuple MockMPPClient::receive( - ::ymmsl::Reference const & receiver) { - last_receiver = receiver; - - auto retval = std::make_tuple( - next_receive_message.encoded(), std::make_tuple( - ProfileTimestamp(1.0), ProfileTimestamp(2.0), - ProfileTimestamp(3.0))); - side_effect(); - return retval; -} - -void MockMPPClient::close() {} - - -void MockMPPClient::reset() { - num_constructed = 0; - next_receive_message.sender = "test.out"; - next_receive_message.receiver = "test2.in"; - next_receive_message.port_length = 0; - next_receive_message.timestamp = 0.0; - next_receive_message.next_timestamp = 1.0; - next_receive_message.message_number = 0; - last_receiver = "_none"; - side_effect = [](){}; // empty lambda function -} - -int MockMPPClient::num_constructed = 0; - -Settings MockMPPClient::make_overlay_() { - Settings s; - s["test2"] = 3.1; - return s; -} - -MPPMessage MockMPPClient::next_receive_message( - "test.out", "test2.in", 0, 0.0, 1.0, make_overlay_(), 0, 9.0, - Data::dict("test1", 12)); - -Reference MockMPPClient::last_receiver("_none"); - -std::function MockMPPClient::side_effect; - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.hpp index e6b14ce1..6d98ab12 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_mpp_client.hpp @@ -1,13 +1,12 @@ #pragma once #include -#include #include #include +#include #include -#include #include #include #include @@ -20,31 +19,27 @@ using ProfileData = std::tuple< ProfileTimestamp, ProfileTimestamp, ProfileTimestamp>; -class MockMPPClient { +class MockMPPClient : public MockClass { public: - MockMPPClient(std::vector const & locations); - MockMPPClient(MockMPPClient const & rhs) = delete; - MockMPPClient & operator=(MockMPPClient const & rhs) = delete; - MockMPPClient(MockMPPClient && rhs) = delete; - MockMPPClient & operator=(MockMPPClient && rhs) = delete; - ~MockMPPClient(); + MockMPPClient(ReturnValue) { + NAME_MOCK_MEM_FUN(MockMPPClient, constructor); + NAME_MOCK_MEM_FUN(MockMPPClient, receive); + NAME_MOCK_MEM_FUN(MockMPPClient, close); + } - std::tuple receive( - ::ymmsl::Reference const & receiver); + MockMPPClient(std::vector const & locations) { + init_from_return_value(); + constructor(locations); + } - void close(); + MockFun const &>> constructor; - // Mock control variables - static void reset(); + MockFun< + Val, std::tuple>, + Val<::ymmsl::Reference const &> + > receive; - static int num_constructed; - static MPPMessage next_receive_message; - static ::ymmsl::Reference last_receiver; - // Called after a mocked receive - static std::function side_effect; - - private: - static ::ymmsl::Settings make_overlay_(); + MockFun close; }; using MPPClient = MockMPPClient; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.cpp deleted file mode 100644 index a5032c53..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include - - -using ymmsl::Conduit; -using ymmsl::Identifier; -using ymmsl::Reference; - - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -MockPeerManager::MockPeerManager( - Reference const & kernel, - std::vector const & index, - std::vector const & conduits, - PeerDims const & peer_dims, - PeerLocations const & peer_locations - ) -{ - ++num_constructed; - last_constructed_kernel_id = kernel; - last_constructed_index = index; - last_constructed_conduits = conduits; - last_constructed_peer_dims = peer_dims; - last_constructed_peer_locations = peer_locations; -} - -bool MockPeerManager::is_connected(Identifier const & port) const { - return is_connected_return_value; -} - -std::vector MockPeerManager::get_peer_ports(Identifier const & port) const { - return get_peer_port_table.at(port); -} - -std::vector MockPeerManager::get_peer_dims( - Reference const & peer_kernel) const { - return get_peer_dims_table.at(peer_kernel); -} - -std::vector MockPeerManager::get_peer_locations( - Reference const & peer_instance) const { - return std::vector({std::string("tcp:test")}); -} - -std::vector MockPeerManager::get_peer_endpoints( - Identifier const & port, - std::vector const & slot - ) const -{ - Reference port_slot(port); - port_slot += slot; - return get_peer_endpoint_table.at(port_slot); -} - -void MockPeerManager::reset() { - num_constructed = 0; - last_constructed_kernel_id = "_none"; - last_constructed_index.clear(); - last_constructed_conduits.clear(); - last_constructed_peer_dims.clear(); - last_constructed_peer_locations.clear(); - - is_connected_return_value = true; - get_peer_port_table.clear(); - get_peer_dims_table.clear(); - get_peer_endpoint_table.clear(); -} - -int MockPeerManager::num_constructed = 0; -Reference MockPeerManager::last_constructed_kernel_id("_none"); -std::vector MockPeerManager::last_constructed_index; -std::vector MockPeerManager::last_constructed_conduits; -PeerDims MockPeerManager::last_constructed_peer_dims; -PeerLocations MockPeerManager::last_constructed_peer_locations; - -bool MockPeerManager::is_connected_return_value; -std::unordered_map> MockPeerManager::get_peer_port_table; -std::unordered_map> MockPeerManager::get_peer_dims_table; -std::unordered_map> MockPeerManager::get_peer_endpoint_table; - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.hpp index 7edcf609..74f3ccc0 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_peer_manager.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -16,45 +17,55 @@ using PeerDims = std::unordered_map>; using PeerLocations = std::unordered_map< ymmsl::Reference, std::vector>; -class MockPeerManager { +class MockPeerManager : public MockClass { public: + MockPeerManager(ReturnValue) { + NAME_MOCK_MEM_FUN(MockPeerManager, constructor); + NAME_MOCK_MEM_FUN(MockPeerManager, is_connected); + NAME_MOCK_MEM_FUN(MockPeerManager, get_peer_ports); + NAME_MOCK_MEM_FUN(MockPeerManager, get_peer_dims); + NAME_MOCK_MEM_FUN(MockPeerManager, get_peer_locations); + NAME_MOCK_MEM_FUN(MockPeerManager, get_peer_endpoints); + } + MockPeerManager( ymmsl::Reference const & kernel, std::vector const & index, std::vector const & conduits, PeerDims const & peer_dims, - PeerLocations const & peer_locations); - - bool is_connected(ymmsl::Identifier const & port) const; - - std::vector get_peer_ports(ymmsl::Identifier const & port) const; - - std::vector get_peer_dims(ymmsl::Reference const & peer_kernel) const; - - std::vector get_peer_locations( - ymmsl::Reference const & peer_instance) const; - - std::vector get_peer_endpoints( - ymmsl::Identifier const & port, - std::vector const & slot) const; - - // Mock control variables - static void reset(); - - static int num_constructed; - static ymmsl::Reference last_constructed_kernel_id; - static std::vector last_constructed_index; - static std::vector last_constructed_conduits; - static PeerDims last_constructed_peer_dims; - static PeerLocations last_constructed_peer_locations; - - static bool is_connected_return_value; - static std::unordered_map> - get_peer_port_table; - static std::unordered_map> - get_peer_dims_table; - static std::unordered_map> - get_peer_endpoint_table; + PeerLocations const & peer_locations) + { + init_from_return_value(); + constructor(kernel, index, conduits, peer_dims, peer_locations); + } + + MockFun, + Val const &>, + Val const &>, + Val, + Val + > constructor; + + MockFun, Val> is_connected; + + MockFun< + Val>, + Val + > get_peer_ports; + + MockFun>, Val> get_peer_dims; + + MockFun< + Val>, + Val + > get_peer_locations; + + MockFun< + Val>, + Val, + Val const &> + > get_peer_endpoints; }; using PeerManager = MockPeerManager; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.cpp deleted file mode 100644 index 028e2c39..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - -int MockPostOffice::handle_request( - char const * res_buf, std::size_t res_len, - std::unique_ptr & response) { - response = std::make_unique( - MPPMessage("test.out", "test2.in", 0, 0.0, 1.0, Data(), 0, 0.0, Data()).encoded()); - return -1; -} - -std::unique_ptr MockPostOffice::get_response(int fd) { - return std::make_unique( - MPPMessage("test.out", "test2.in", 0, 0.0, 1.0, Data(), 0, 8.0, Data()).encoded()); -} - -void MockPostOffice::deposit( - ymmsl::Reference const & receiver, - std::unique_ptr message) { - last_receiver = receiver; - last_message = std::make_unique(MPPMessage::from_bytes(*message)); -} - -void MockPostOffice::wait_for_receivers() const { - -} - -void MockPostOffice::reset() { - last_receiver = "_none"; - last_message.reset(); -} - -Reference MockPostOffice::last_receiver("_none"); -std::unique_ptr MockPostOffice::last_message; - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.hpp index a275ae9d..3bc45db0 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_post_office.hpp @@ -8,33 +8,89 @@ #include #include +#include + + +namespace mock_post_office { + +using ::libmuscle::_MUSCLE_IMPL_NS::DataConstRef; +using ::libmuscle::_MUSCLE_IMPL_NS::MPPMessage; + +struct EncodedMessage { + using ArgType = std::unique_ptr; + using StorageType = std::shared_ptr; + + static StorageType arg_to_store(ArgType const & message) { + return std::make_shared(MPPMessage::from_bytes(*message)); + } + + static ArgType store_to_arg(StorageType const & stored) { + return std::make_unique(stored->encoded()); + } +}; + +struct EncodedMessageOut { + using ArgType = std::unique_ptr &; + using StorageType = DataConstRef *; + + static StorageType arg_to_store(ArgType const & buffer) { + return buffer.get(); + } +}; + +} + namespace libmuscle { namespace _MUSCLE_IMPL_NS { -class MockPostOffice : public mcp::RequestHandler { +class MockPostOffice : public MockClass, public mcp::RequestHandler { public: - MockPostOffice() = default; + MockPostOffice(ReturnValue) { + NAME_MOCK_MEM_FUN(MockPostOffice, constructor); + NAME_MOCK_MEM_FUN(MockPostOffice, handle_request_mock); + NAME_MOCK_MEM_FUN(MockPostOffice, get_response_mock); + NAME_MOCK_MEM_FUN(MockPostOffice, deposit); + NAME_MOCK_MEM_FUN(MockPostOffice, wait_for_receivers); + } + + MockPostOffice() { + init_from_return_value(); + constructor(); + } + + MockFun constructor; + + /* Virtual member functions cannot be overridden by an object, so we override + * them with a function and then forward to the mock. + */ + MockFun< + Val, + Val, Val, + ::mock_post_office::EncodedMessageOut + > handle_request_mock; virtual int handle_request( char const * req_buf, std::size_t req_len, - std::unique_ptr & res_buf) override; + std::unique_ptr & res_buf) override + { + return std::move(handle_request_mock(req_buf, req_len, res_buf)); + } - virtual std::unique_ptr get_response(int fd) override; + MockFun<::mock_post_office::EncodedMessage, Val> get_response_mock; - void deposit( - ymmsl::Reference const & receiver, - std::unique_ptr message); + virtual std::unique_ptr get_response(int fd) override { + return get_response_mock(fd); + } - void wait_for_receivers() const; + MockFun, ::mock_post_office::EncodedMessage + > deposit; - // Mock control variables - static void reset(); - - static ymmsl::Reference last_receiver; - static std::unique_ptr last_message; + MockFun wait_for_receivers; }; + using PostOffice = MockPostOffice; } } diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.cpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.cpp deleted file mode 100644 index b4429fa6..00000000 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "mocks/mock_profiler.hpp" - -#include - - -namespace libmuscle { namespace _MUSCLE_IMPL_NS { - - MockProfiler::MockProfiler() {} - - MockProfiler::MockProfiler( - MMPClient & manager) {} - - void MockProfiler::shutdown() {} - - void MockProfiler::set_level(std::string const & level) {} - - void MockProfiler::record_event(ProfileEvent && event) {} - -} } - diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.hpp index 528ce01f..408efbd4 100644 --- a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.hpp +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_profiler.hpp @@ -3,23 +3,38 @@ #include #include #include +#include #include namespace libmuscle { namespace _MUSCLE_IMPL_NS { -class MockProfiler { +class MockProfiler : public MockClass { public: - MockProfiler(); + MockProfiler(ReturnValue) { + NAME_MOCK_MEM_FUN(MockProfiler, constructor); + NAME_MOCK_MEM_FUN(MockProfiler, shutdown); + NAME_MOCK_MEM_FUN(MockProfiler, set_level); + NAME_MOCK_MEM_FUN(MockProfiler, record_event); + } - MockProfiler(MMPClient & manager); + MockProfiler() { + init_from_return_value(); + } - void shutdown(); + MockProfiler(MMPClient & manager) { + init_from_return_value(); + constructor(manager); + } - void set_level(std::string const & level); + MockFun> constructor; - void record_event(ProfileEvent && event); + MockFun shutdown; + + MockFun> set_level; + + MockFun> record_event; }; using Profiler = MockProfiler; diff --git a/libmuscle/cpp/src/libmuscle/tests/mocks/mock_support.hpp b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_support.hpp new file mode 100644 index 00000000..754ae829 --- /dev/null +++ b/libmuscle/cpp/src/libmuscle/tests/mocks/mock_support.hpp @@ -0,0 +1,563 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include + + +namespace mock_support { + +template +struct remove_ptr_ref_const { + using type = typename std::remove_cv< + typename std::remove_pointer< + typename std::remove_reference::type>::type>::type; +}; + + +template +struct StorageType { + using type = std::tuple; +}; + + +// convert an argument to the given type by (de)referencing as needed +template +T convert(T const & t) { + return t; +} + +template +typename std::enable_if::value, T>::type +convert(typename std::remove_reference::type * t) { + return *t; +} + +template +typename std::enable_if::value, T const>::type +convert(typename std::remove_pointer::type const & t) { + return &t; +} + +template +typename std::enable_if::value, T>::type +convert(typename std::remove_pointer::type & t) { + return &t; +} + + +// convert Args to storage types and construct a tuple of them +template +typename StorageType::type store(typename Args::ArgType... args) +{ + return std::tuple(Args::arg_to_store(args)...); +} + +} // namespace mock_support + + +/* Tag base type for arguments and return types. */ +template +struct TagBase { + using ArgType = ArgT; + using StorageType = StorageT; + + static StorageType arg_to_store(ArgType const & arg) { + return ::mock_support::convert(arg); + } + + static ArgType store_to_arg(StorageType const & stored) { + return ::mock_support::convert(stored); + } +}; + + +/* Tag for value types. + * + * These are copyable and will be stored in the call args list by value. + * + * @tparam T The type of the return value or parameter. + * @tparam ST Override the storage type. ST must be convertible to T. + */ +template < + typename T, + typename ST = typename ::mock_support::remove_ptr_ref_const::type +> +struct Val : TagBase {}; + + +struct objects_must_be_passed_by_reference_or_pointer; + + +/* Tag for object types. + * + * These are not copyable (or at least don't need to be) and will be stored in the call + * args list by pointer. + * + * @tparam T The type of the return value or parameter. + */ +template +struct Obj : TagBase { +}; + +template +struct Obj::value>::type> + : TagBase::type> +{}; + +template +struct Obj::value>::type> + : TagBase::type>::type *> +{}; + + +/* Tag for void return type. + * + * This should be used as the first template argument to MockFun if the function to + * be mocked returns void. + */ +struct Void { + using ArgType = void; + using StorageType = Void; +}; + + +/* A mock (member) function. + * + * This is a class template that mocks a (member) function with a given signature. + * Once an object is created you can set the return value, or specify a function that + * creates a return value. The mock will track any calls so you can assert that they + * did or didn't happen. + * + * This is intended to partially mirror Python's unittest.mock.Mock so that we can keep + * the tests similar between Python and C++. + * + * + * Creating mock functions + * + * To specify the type of the mocked function, use the template parameters. The first + * template argument specifies the return type, after that follow the argument types. + * Each argument needs to be wrapped in the Val<> or Obj<> template. + * + * Val<> is used for value types, which must be copyable and moveable. Use this if the + * type is one that carries data. The base type will be used to record any passed + * arguments, or for the return_value member. + * + * Obj<> is used for objects, e.g. when you're passing a reference to another component. + * Any passed values will be recorded in a pointer pointing to them, and the + * return_value member variable will also be a pointer type. + * + * For functions returning void, use Void as the return type tag. + * + * + * Examples + * + * As an example, if you had a function f with signature + * + * int f(std::string const & s, Communicator & c); + * + * then you should mock it using + * + * MockFun, Val, Obj> f; + * + * f.call_args_list will then be of type + * std::vector> and return_value will be an int. + * + * MockFun, Obj> f2; + * + * mocks a function with signature + * + * void f2(std::string s, Communicator const * s); + * + * f2's call_args_list will then be of the same type as that of f, and its return_value + * will be of a type that cannot be assigned to. + * + * Note that we used Val<> on the string argument, since a string is a copyable value + * and it makes sense to just store it as a string. If we used Obj<> here, then a + * pointer to the passed string would be stored, which could be problematic if the + * caller passes a local variable that has gone out of scope by the time we get back to + * our test function to examine the arguments. + * + * The Communicator object isn't copyable, and we know there's only one of it that lives + * as long as the test is running, so we can use Obj<> for it. + * + * + * Functions with default arguments + * + * If the function to be mocked has default arguments, then you'll need to create a + * derived class with an overload for operator() for the shorter versions: + * + * using BaseMockFun = MockFun, Val, Val>; + * + * struct MockOverloadedFun : BaseMockFun { + * int operator()(std::string const & s = "", bool b = true) { + * return BaseMockFun::operator()(s, b); + * } + * }; + * + * Or you can rename the mock and define a separate function with default arguments + * that forwards to it: + * + * MockFun f_mock; + * + * int f(std::string const & s = "", bool b = true) { + * f_mock(s, b); + * } + * + * If you're mocking a virtual member function override then the first option doesn't + * work, and the second one needs to be used. + * + * + * Using the mocked function + * + * To set a return value for the mocked function f above, use + * + * f.return_value = 7; + * + * If the code under test then does + * + * int x = f("test", communicator); + * + * x will become 7. Once back in the test, we can check that f was called and how using + * + * ASSERT_TRUE(f.called()); + * ASSERT_TRUE(f.called_once()); + * ASSERT_EQ(f.call_arg<0>(), "test"); + * ASSERT_EQ(f.call_arg<1>(), &communicator); + * + * To specify a function that should be called when f2 is called, do + * + * f2.side_effect = [](std::string s, Communicator const * s) { + * std::cout << "Called with " << s << std::endl; + * }; + * + * Don't forget the semicolon at the end, and don't forget to return a value if the + * function doesn't return void. + * + * @tparam Ret Type returned by the member function to be mocked, wrapped in Val or Obj. + * @tparam Args... The types of the arguments to be accepted by the mock, also wrapped. + */ +template +class MockFun { + public: + using StorageType = typename ::mock_support::StorageType::type; + using ReturnStorageType = typename std::remove_const::type; + + /* Mock call operator for non-void functions. */ + template + typename std::enable_if::value, typename R::ArgType>::type + operator()(typename Args::ArgType ... args) const { + call_args_list.push_back(::mock_support::store(args...)); + if (side_effect) + return side_effect(std::forward(args)...); + else { + if (!return_value.is_set()) { + throw std::runtime_error( + name + " was called but doesn't have a return_value" + " or side_effect set"); + } + return R::store_to_arg(return_value.get()); + } + } + + /* Mock call operator for void return type. */ + template + typename std::enable_if::value>::type + operator()(typename Args::ArgType ... args) const + { + call_args_list.push_back( + ::mock_support::store( + std::forward(args)...)); + if (side_effect) + side_effect(std::forward(args)...); + } + + /* Returns whether the mock was called at least once. */ + bool called() const { + return !call_args_list.empty(); + } + + /* Returns whether the mock was called exactly once. */ + bool called_once() const { + return call_args_list.size() == 1u; + } + + /* Returns whether the last call was done with the given arguments. */ + template + bool called_with(A... args) const { + return call_args() == ::mock_support::store(args...); + } + + /* Returns whether there was exactly one call with the given arguments. */ + template + bool called_once_with(A... args) const { + return called_once() && called_with(args...); + } + + /* Returns the arguments with which the mock was called most recently. + * + * Getting individual items out of the tuple requires some ugly syntax, so you + * probably want to use call_arg(j) instead, see below. + */ + StorageType const & call_args() const { + if (call_args_list.empty()) { + throw std::runtime_error( + "Tried to get call_args() for " + name + ", but call_args_list" + " is empty"); + } + return call_args_list.back(); + } + + /* Returns the I'th argument of the j'th most recent call. + * + * Counts start at 0 as usual, so f.call_arg<0>(1) gets the first argument of + * the second most recent call. + */ + template + std::tuple_element_t const & call_arg( + std::size_t j = 0u) const + { + if (j >= call_args_list.size()) { + std::ostringstream oss; + oss << "Requested " << name << ".call_arg<" << I << ">(" << j << ")"; + oss << " but " << name; + if (call_args_list.empty()) + oss << " has never been called."; + else { + oss << " has only been called " << call_args_list.size(); + oss << " times."; + } + throw std::runtime_error(oss.str()); + } + std::size_t i = call_args_list.size() - 1u - j; + return std::get(call_args_list.at(i)); + } + + /* The value to be returned when the mock is called. */ + ::libmuscle::_MUSCLE_IMPL_NS::Optional return_value; + + /* A function to be called whose result to return when called. */ + std::function side_effect; + + /* All the arguments with which the function has been called. */ + mutable std::vector call_args_list; + + /* Name of the mocked function, used for error messages. + * + * See NAME_MOCK_FUN and NAME_MOCK_MEM_FUN for some helpers. + * */ + std::string name; +}; + + +/* Set the name of a mock function. + * + * Use this to teach a mock what it's called, so that it can give you better error + * messages. Not required, but very useful. + * + * For member functions there's NAME_MOCK_MEM_FUN, which also lets you specify the + * class name. + */ +#define NAME_MOCK_FUN(FUNC) FUNC.name = #FUNC + + +/* Set the name of a mock member function. + * + * Use this to teach a mock what it's called, so that it can give you better error + * messages. Not required, but very useful. See MockClass for an example. + */ +#define NAME_MOCK_MEM_FUN(CLS, FUNC) FUNC.name = #CLS "::" #FUNC + + +/* Tag type for return value constructor. */ +struct ReturnValue {}; + + +/* Parent class for mock classes. + * + * This adds facilities to a mock class for setting a default object to be cloned when a + * new object is created. + * + * If you have a function under test that creates an object of a mocked class and then + * calls a member function on it, then you can't set the return value on that object + * because that would have to happen in between those two actions, and they're inside + * the function. + * + * In Python, you can say MyClass = Mock(), and then MyClass.return_value = Mock(), and + * now that second mock will be returned when a new MyClass is made. You can then do + * MyClass.return_value.mem_fun = Mock() and MyClass.return_value.mem_fun.return_value = + * 10, and now the newly created object will return 10 from its mem_fun(). + * + * This class has a static return_value member of type T, and is intended to be + * inherited from using the Curiously Recurring Template Pattern (CRTP): + * + * class MockMyClass : public MockClass { + * public: + * MockMyClass(ReturnValue) { + * NAME_MOCK_MEM_FUN(MockMyClass, constructor); + * NAME_MOCK_MEM_FUN(MockMyClass, fn1); + * NAME_MOCK_MEM_FUN(MockMyClass, fn2); + * } + * + * MockMyClass() { + * init_from_return_value(); + * } + * + * MockMyClass(int i) { + * init_from_return_value(); + * constructor(i); + * } + * + * MockFun constructor; + * MockFun> fn1; + * MockFun> fn2; + * }; + * + * Now, a test can do + * + * MockMyClass::return_value.fn1.return_value = 10; + * + * and then when the test code does + * + * MockMyClass my_class; + * int x = my_class.fn1(); + * + * it will receive that 10. + * + * The mock class must define a constructor taking a ReturnValue, as shown above. The + * argument is just a tag to distinguish this from other constructors you may add to + * mimick the original type. This constructor is used to initialise the ::return_value + * static member variable. You should use NAME_MOCK_MEM_FUN here for each MockFun<> in + * the class so that the mock member functions know who they are and can generate + * helpful error messages. Other initialisation (e.g. setting default return values) is + * better done in a fixture, so don't add that here. + * + * Other constructors can then be added as needed. You'll generally want a default + * constructor so that an instance of the mock class can easily be made in a fixture or + * a test, and of course you need to add a constructor mimicking each constructor the + * real class has. Since you can't have a member variable with the same name as the + * class, the constructor needs to be an actual constructor. In the example above, we + * have it forward its arguments to a mock, so that a test can check what it was called + * with. + * + * Each normal constructor (but not the ReturnValue one!) needs to call + * init_from_return_value() as the first thing it does. This will assign the static + * ::return_value to the current object, thus ensuring that anything set on that by the + * user is applied. + * + * With the constructors done, you'll probably want to add some MockFun<> member + * variables that mock functions an the real class. See MockFun<> above. + * + * Finally, note that the static return_value is shared by everyone using this mock in + * this process. If multiple tests are run in a single program, as is usually the case, + * then the mock needs to be reset between tests to ensure they're independent. See + * RESET_MOCKS below for this. + */ +template +struct MockClass { + protected: + /* Initialise this object from the static return_value. + * + * To be called in constructors of derived mock classes. + */ + void init_from_return_value() { + static_cast(*this) = return_value; + } + + public: + /* Reset return_value. + * + * Since return_value is a static variable, there's only one for the entire + * program, meaning tests share it. If a test modifies the return_value, then + * this affects all the other tests, and that's bad. To avoid this, call + * T::reset(); in the fixture constructor. + */ + struct reset { + reset() { + T::return_value = T(ReturnValue()); + } + }; + + /* Return value of T(). + * + * This is the return value of the *type* T. If a T is created by the test + * code, then a copy of this value will be returned, assuming that T has been + * implemented correctly. + * + * Currently static and therefore not thread-safe. We could make this + * thread_local instead, which would allow running tests in parallel, but there + * are some issues with thread_local on MacOS so we'll need to test carefully. + * For now this is good enough. + */ + static T return_value; + + // TODO: we could have a static list of instances and add new ones here +}; + +template +T MockClass::return_value = T(ReturnValue()); + + + +/* Return value reset helper. + * + * The class return value of a MockClass is a static member, which means that it gets + * created at start-up of the test program and is destroyed at the end. That means that + * if multiple tests are run in succession, they share the return_value. This is not + * good, because it introduces a dependency. Unfortunately, the class itself has static + * storage duration in a way, and we can't make a new class for every test like we can + * in Python. + * + * So instead, we need to reset the return value in between tests. This can be done in + * the fixture class constructor, but it's a bit tricky because we want to reset the + * return value before instantiating the mock. If we did + * + * struct fixture { + * Mock mock_; + * + * fixture() { + * Mock::reset(); + * } + * }; + * + * then mock_ would be created as a copy of whatever the previous test left in + * Mock::return_value, and only then Mock::return_value would be reset. + * + * So instead, Mock::reset is defined as a struct (see MockClass), and we create a + * variable of that type: + * + * struct fixture { + * Mock::reset _; + * Mock mock_; + * + * fixture() { + * // initialise things + * } + * }; + * + * Since local variables are initialised in the order in which they are declared, the + * constructor of Mock::reset (which resets the static return value) will be run before + * mock_ is instantiated. + * + * If you're using multiple mocks, then you'll want to reset all of them. Then you'll + * have to create multiple dummy variables with different names, and it all becomes + * wordy and unclear. So we have a helper here that lets you write + * + * struct fixture { + * RESET_MOCKS(Mock1, Mock2, Mock3); + * }; + * + * Put that at the top of your fixture class and all should be well. + */ +template +using reset_mocks_t = std::tuple; + +#define RESET_MOCKS(...) reset_mocks_t<__VA_ARGS__> _ + diff --git a/libmuscle/cpp/src/libmuscle/tests/test_communicator.cpp b/libmuscle/cpp/src/libmuscle/tests/test_communicator.cpp index aa2ba96f..f33af689 100644 --- a/libmuscle/cpp/src/libmuscle/tests/test_communicator.cpp +++ b/libmuscle/cpp/src/libmuscle/tests/test_communicator.cpp @@ -6,7 +6,7 @@ #define LIBMUSCLE_MOCK_POST_OFFICE #define LIBMUSCLE_MOCK_PROFILER -// into the real implementation, +// into the real implementation under test. #include #include @@ -24,25 +24,18 @@ #include #include -// then add mock implementations as needed. -#include -#include -#include -#include -#include -#include - - // Test code dependencies #include #include #include +#include #include #include #include #include #include #include +#include #include @@ -54,14 +47,16 @@ using libmuscle::_MUSCLE_IMPL_NS::PeerDims; using libmuscle::_MUSCLE_IMPL_NS::PeerLocations; using libmuscle::_MUSCLE_IMPL_NS::PortsDescription; using libmuscle::_MUSCLE_IMPL_NS::Message; -using libmuscle::_MUSCLE_IMPL_NS::MockLogger; +using libmuscle::_MUSCLE_IMPL_NS::MockMPPClient; using libmuscle::_MUSCLE_IMPL_NS::MockPeerManager; using libmuscle::_MUSCLE_IMPL_NS::MockPostOffice; using libmuscle::_MUSCLE_IMPL_NS::MockProfiler; -using libmuscle::_MUSCLE_IMPL_NS::MockMPPClient; using libmuscle::_MUSCLE_IMPL_NS::mcp::MockTcpTransportServer; +using libmuscle::_MUSCLE_IMPL_NS::MPPMessage; +using libmuscle::_MUSCLE_IMPL_NS::ProfileTimestamp; using ymmsl::Conduit; +using ymmsl::Identifier; using ymmsl::Reference; @@ -71,125 +66,184 @@ int main(int argc, char *argv[]) { } -/* Mocks have internal state, which needs to be reset before each test. This - * means that the tests are not reentrant, and cannot be run in parallel. - * It's all fast enough, so that's not a problem. - */ -void reset_mocks() { - MockPeerManager::reset(); - MockMPPClient::reset(); - MockTcpTransportServer::reset(); -} +struct libmuscle_communicator : ::testing::Test { + RESET_MOCKS( + ::libmuscle::_MUSCLE_IMPL_NS::MockLogger, + ::libmuscle::_MUSCLE_IMPL_NS::MockMPPClient, + ::libmuscle::_MUSCLE_IMPL_NS::MockPeerManager, + ::libmuscle::_MUSCLE_IMPL_NS::MockPostOffice, + ::libmuscle::_MUSCLE_IMPL_NS::MockProfiler, + ::libmuscle::_MUSCLE_IMPL_NS::mcp::MockTcpTransportServer); -MockLogger & mock_logger() { - static MockLogger logger; - return logger; -} + ::libmuscle::_MUSCLE_IMPL_NS::MockLogger mock_logger_; + ::libmuscle::_MUSCLE_IMPL_NS::MockProfiler mock_profiler_; -MockProfiler & mock_profiler() { - static MockProfiler profiler; - return profiler; -} + MPPMessage next_received_message; -std::unique_ptr connected_communicator() { - std::unique_ptr comm(new Communicator( - Reference("kernel"), {13}, {}, mock_logger(), mock_profiler())); + libmuscle_communicator() + : next_received_message( + "test.out", "test2.in", 0, 0.0, 1.0, Settings({{"test2", 3.1}}), + 0, 9.0, Data::dict("test1", 12)) + { + MockPeerManager::return_value.is_connected.return_value = true; - std::vector conduits({ - Conduit("kernel.out", "other.in"), - Conduit("other.out", "kernel.in")}); + auto & ret_val = MockMPPClient::return_value; - PeerDims peer_dims({{Reference("other"), {1}}}); + using RecvRet = std::tuple< + DataConstRef, std::tuple< + ProfileTimestamp, ProfileTimestamp, ProfileTimestamp>>; - PeerLocations peer_locations({ - {Reference("other"), {"tcp:test"}}}); + ret_val.receive.side_effect = [this](Reference const &) -> RecvRet { + return std::make_tuple( + next_received_message.encoded(), std::make_tuple( + ProfileTimestamp(1.0), ProfileTimestamp(2.0), + ProfileTimestamp(3.0))); + }; + } - MockPeerManager::get_peer_dims_table.emplace("other", std::vector({1})); - MockPeerManager::get_peer_endpoint_table.emplace("out", - std::vector({Endpoint("other", {}, "in", {13})})); - MockPeerManager::get_peer_endpoint_table.emplace("in", - std::vector({Endpoint("other", {}, "out", {13})})); + std::unique_ptr connected_communicator() { + std::unique_ptr comm(new Communicator( + Reference("kernel"), {13}, {}, mock_logger_, mock_profiler_)); - comm->connect(conduits, peer_dims, peer_locations); - return std::move(comm); -} + std::vector conduits({ + Conduit("kernel.out", "other.in"), + Conduit("other.out", "kernel.in")}); -std::unique_ptr connected_communicator2() { - std::unique_ptr comm(new Communicator( - Reference("other"), {}, {}, mock_logger(), mock_profiler())); + PeerDims peer_dims({{Reference("other"), {1}}}); - std::vector conduits({ - Conduit("kernel.out", "other.in"), - Conduit("other.out", "kernel.in")}); + PeerLocations peer_locations({ + {Reference("other"), {"tcp:test"}}}); - PeerDims peer_dims({{Reference("kernel"), {20}}}); + auto & peer_manager = MockPeerManager::return_value; + peer_manager.get_peer_dims.return_value = std::vector({1}); + peer_manager.get_peer_endpoints.side_effect = [] + (Identifier const & port, std::vector const & slot) + { + Reference port_slot(port); + port_slot += slot; - PeerLocations peer_locations({ - {Reference("kernel"), {"tcp:test"}}}); + if (port_slot == "out") + return std::vector({Endpoint("other", {}, "in", {13})}); + if (port_slot == "in") + return std::vector({Endpoint("other", {}, "out", {13})}); + throw std::runtime_error( + "Invalid port/slot " + std::string(port_slot) + " in get_peer_endpoints"); + }; - MockPeerManager::get_peer_dims_table.emplace("kernel", std::vector({20})); - MockPeerManager::get_peer_endpoint_table.emplace("in[13]", - std::vector({Endpoint("kernel", {13}, "out", {})})); - MockPeerManager::get_peer_endpoint_table.emplace("out[13]", - std::vector({Endpoint("kernel", {13}, "in", {})})); + peer_manager.get_peer_locations.return_value = std::vector( + {std::string("tcp:test")}); - comm->connect(conduits, peer_dims, peer_locations); - return std::move(comm); -} + comm->connect(conduits, peer_dims, peer_locations); + return comm; + } -std::unique_ptr connected_communicator3() { - PortsDescription desc({ - {Operator::O_I, {"out[]"}}, - {Operator::S, {"in[]"}} - }); + std::unique_ptr connected_communicator2() { + std::unique_ptr comm(new Communicator( + Reference("other"), {}, {}, mock_logger_, mock_profiler_)); - std::unique_ptr comm(new Communicator( - Reference("kernel"), {}, desc, mock_logger(), mock_profiler())); + std::vector conduits({ + Conduit("kernel.out", "other.in"), + Conduit("other.out", "kernel.in")}); - std::vector conduits({ - Conduit("kernel.out", "other.in"), - Conduit("other.out", "kernel.in")}); + PeerDims peer_dims({{Reference("kernel"), {20}}}); - PeerDims peer_dims({{Reference("other"), {}}}); + PeerLocations peer_locations({ + {Reference("kernel"), {"tcp:test"}}}); - PeerLocations peer_locations({ - {Reference("other"), {"tcp:test"}}}); + auto & peer_manager = MockPeerManager::return_value; + peer_manager.get_peer_dims.return_value = std::vector({20}); + peer_manager.get_peer_endpoints.side_effect = [] + (Identifier const & port, std::vector const & slot) + { + Reference port_slot(port); + port_slot += slot; - MockPeerManager::get_peer_dims_table.emplace("other", std::vector({})); - MockPeerManager::get_peer_endpoint_table.emplace("out[13]", - std::vector({Endpoint("other", {}, "in", {13})})); - MockPeerManager::get_peer_endpoint_table.emplace("in[13]", - std::vector({Endpoint("other", {}, "out", {13})})); - MockPeerManager::get_peer_port_table.emplace("out", - std::vector({"other.in"})); - MockPeerManager::get_peer_port_table.emplace("in", - std::vector({"other.out"})); - - comm->connect(conduits, peer_dims, peer_locations); - return std::move(comm); -} + if (port_slot == "in[13]") + return std::vector({Endpoint("kernel", {13}, "out", {})}); + if (port_slot == "out[13]") + return std::vector({Endpoint("kernel", {13}, "in", {})}); + throw std::runtime_error( + "Invalid port/slot " + std::string(port_slot) + " in get_peer_endpoints"); + }; + + peer_manager.get_peer_locations.return_value = std::vector( + {std::string("tcp:test")}); + comm->connect(conduits, peer_dims, peer_locations); + return comm; + } + + std::unique_ptr connected_communicator3() { + PortsDescription desc({ + {Operator::O_I, {"out[]"}}, + {Operator::S, {"in[]"}} + }); + + std::unique_ptr comm(new Communicator( + Reference("kernel"), {}, desc, mock_logger_, mock_profiler_)); + + std::vector conduits({ + Conduit("kernel.out", "other.in"), + Conduit("other.out", "kernel.in")}); + + PeerDims peer_dims({{Reference("other"), {}}}); + + PeerLocations peer_locations({ + {Reference("other"), {"tcp:test"}}}); + + auto & peer_manager = MockPeerManager::return_value; + peer_manager.get_peer_dims.return_value = std::vector({}); + peer_manager.get_peer_endpoints.side_effect = [] + (Identifier const & port, std::vector const & slot) + { + Reference port_slot(port); + port_slot += slot; + + if (port_slot == "in[13]") + return std::vector({Endpoint("other", {}, "out", {13})}); + if (port_slot == "out[13]") + return std::vector({Endpoint("other", {}, "in", {13})}); + throw std::runtime_error( + "Invalid port/slot " + std::string(port_slot) + " in get_peer_endpoints"); + }; + + peer_manager.get_peer_ports.side_effect = [](Identifier const & port) { + if (port == "out") + return std::vector({"other.in"}); + if (port == "in") + return std::vector({"other.out"}); + throw std::runtime_error( + "Invalid port " + port + " in get_peer_ports"); + }; + + peer_manager.get_peer_locations.return_value = std::vector( + {std::string("tcp:test")}); + + comm->connect(conduits, peer_dims, peer_locations); + return comm; + } +}; -TEST(libmuscle_communicator, create_communicator) { - reset_mocks(); + +TEST_F(libmuscle_communicator, create_communicator) { Communicator comm( - Reference("kernel"), {13}, {}, mock_logger(), mock_profiler()); - ASSERT_EQ(MockTcpTransportServer::num_constructed, 1); - ASSERT_EQ(MockMPPClient::num_constructed, 0); + Reference("kernel"), {13}, {}, mock_logger_, mock_profiler_); + ASSERT_EQ(comm.servers_.size(), 1); + ASSERT_TRUE(comm.clients_.empty()); } -TEST(libmuscle_communicator, get_locations) { - reset_mocks(); +TEST_F(libmuscle_communicator, get_locations) { Communicator comm( - Reference("kernel"), {13}, {}, mock_logger(), mock_profiler()); + Reference("kernel"), {13}, {}, mock_logger_, mock_profiler_); + auto server = dynamic_cast(comm.servers_.back().get()); + server->get_location_mock.return_value = "tcp:test_location"; ASSERT_EQ(comm.get_locations().size(), 1); ASSERT_EQ(comm.get_locations()[0], "tcp:test_location"); } -TEST(libmuscle_communicator, test_connect) { - reset_mocks(); +TEST_F(libmuscle_communicator, test_connect) { Communicator comm( - Reference("kernel"), {13}, {}, mock_logger(), mock_profiler()); + Reference("kernel"), {13}, {}, mock_logger_, mock_profiler_); std::vector conduits({ Conduit("kernel.out", "other.in"), @@ -198,11 +252,13 @@ TEST(libmuscle_communicator, test_connect) { PeerLocations peer_locations({ {Reference("other"), {"tcp:test"}}}); - MockPeerManager::get_peer_dims_table.emplace("other", std::vector({1})); + MockPeerManager::return_value.get_peer_dims.return_value = std::vector({1}); + MockPeerManager::return_value.is_connected.return_value = true; comm.connect(conduits, peer_dims, peer_locations); - ASSERT_EQ(MockPeerManager::last_constructed_kernel_id, "kernel"); - ASSERT_EQ(MockPeerManager::last_constructed_index, std::vector({13})); + ASSERT_EQ(comm.peer_manager_->get_peer_dims.call_arg<0>(), "other"); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<0>(), "kernel"); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<1>(), std::vector({13})); // check inferred ports auto const & ports = comm.ports_; @@ -216,16 +272,14 @@ TEST(libmuscle_communicator, test_connect) { ASSERT_FALSE(ports.at("out").is_vector()); } -TEST(libmuscle_communicator, test_connect_vector_ports) { - reset_mocks(); - +TEST_F(libmuscle_communicator, test_connect_vector_ports) { PortsDescription desc({ {Operator::F_INIT, {"in[]"}}, {Operator::O_F, {"out1", "out2[]"}} }); Communicator comm( - Reference("kernel"), {13}, desc, mock_logger(), mock_profiler()); + Reference("kernel"), {13}, desc, mock_logger_, mock_profiler_); std::vector conduits({ Conduit("other1.out", "kernel.in"), @@ -243,22 +297,35 @@ TEST(libmuscle_communicator, test_connect_vector_ports) { {Reference("other3"), {"tcp:test3"}} }); - MockPeerManager::get_peer_port_table.emplace("in", - std::vector({"other1.out"})); - MockPeerManager::get_peer_port_table.emplace("out1", - std::vector({"other.in"})); - MockPeerManager::get_peer_port_table.emplace("out2", - std::vector({"other3.in"})); + MockPeerManager::return_value.get_peer_ports.side_effect = [] + (Identifier const & port) + { + if (port == "in") + return std::vector({"other1.out"}); + if (port == "out1") + return std::vector({"other.in"}); + if (port == "out2") + return std::vector({"other3.in"}); + throw std::runtime_error("Unexpected port " + port + " in get_peer_ports"); + }; - MockPeerManager::get_peer_dims_table.emplace("other1", std::vector({20, 7})); - MockPeerManager::get_peer_dims_table.emplace("other", std::vector({25})); - MockPeerManager::get_peer_dims_table.emplace("other3", std::vector({20})); + MockPeerManager::return_value.get_peer_dims.side_effect = [] + (Reference const & peer_kernel) + { + if (peer_kernel == "other1") + return std::vector({20, 7}); + if (peer_kernel == "other") + return std::vector({25}); + if (peer_kernel == "other3") + return std::vector({20}); + throw std::runtime_error("Unexpected kernel in get_peer_dims"); + }; comm.connect(conduits, peer_dims, peer_locations); - ASSERT_EQ(MockPeerManager::last_constructed_conduits, conduits); - ASSERT_EQ(MockPeerManager::last_constructed_peer_dims, peer_dims); - ASSERT_EQ(MockPeerManager::last_constructed_peer_locations, peer_locations); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<2>(), conduits); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<3>(), peer_dims); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<4>(), peer_locations); auto const & ports = comm.ports_; @@ -279,15 +346,13 @@ TEST(libmuscle_communicator, test_connect_vector_ports) { ASSERT_TRUE(ports.at("out2").is_resizable()); } -TEST(libmuscle_communicator, test_connect_multidimensional_ports) { - reset_mocks(); - +TEST_F(libmuscle_communicator, test_connect_multidimensional_ports) { PortsDescription desc({ {Operator::F_INIT, {"in[][]"}} }); Communicator comm( - Reference("kernel"), {13}, desc, mock_logger(), mock_profiler()); + Reference("kernel"), {13}, desc, mock_logger_, mock_profiler_); std::vector conduits({ Conduit("other.out", "kernel.in") @@ -301,24 +366,22 @@ TEST(libmuscle_communicator, test_connect_multidimensional_ports) { {Reference("other"), {"tcp:test"}} }); - MockPeerManager::get_peer_port_table.emplace("in", - std::vector({"other.out"})); - MockPeerManager::get_peer_dims_table.emplace("other", std::vector({20, 7, 30})); + MockPeerManager::return_value.get_peer_ports.return_value = std::vector( + {"other.out"}); + MockPeerManager::return_value.get_peer_dims.return_value = std::vector({20, 7, 30}); ASSERT_THROW( comm.connect(conduits, peer_dims, peer_locations), std::invalid_argument); - ASSERT_EQ(MockPeerManager::last_constructed_conduits, conduits); - ASSERT_EQ(MockPeerManager::last_constructed_peer_dims, peer_dims); - ASSERT_EQ(MockPeerManager::last_constructed_peer_locations, peer_locations); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<2>(), conduits); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<3>(), peer_dims); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<4>(), peer_locations); } -TEST(libmuscle_communicator, test_connect_inferred_ports) { - reset_mocks(); - +TEST_F(libmuscle_communicator, test_connect_inferred_ports) { Communicator comm( - Reference("kernel"), {13}, {}, mock_logger(), mock_profiler()); + Reference("kernel"), {13}, {}, mock_logger_, mock_profiler_); std::vector conduits({ Conduit("other1.out", "kernel.in"), @@ -338,22 +401,35 @@ TEST(libmuscle_communicator, test_connect_inferred_ports) { {Reference("other2"), {"tcp:test2"}} }); - MockPeerManager::get_peer_port_table.emplace("in", - std::vector({"other1.out"})); - MockPeerManager::get_peer_port_table.emplace("out1", - std::vector({"other.in"})); - MockPeerManager::get_peer_port_table.emplace("out3", - std::vector({"other2.in"})); + MockPeerManager::return_value.get_peer_ports.side_effect = [] + (Identifier const & port) + { + if (port == "in") + return std::vector({"other1.out"}); + if (port == "out1") + return std::vector({"other.in"}); + if (port == "out3") + return std::vector({"other2.in"}); + throw std::runtime_error("Unexpected port in get_peer_ports"); + }; - MockPeerManager::get_peer_dims_table.emplace("other1", std::vector({20, 7})); - MockPeerManager::get_peer_dims_table.emplace("other", std::vector({25})); - MockPeerManager::get_peer_dims_table.emplace("other2", std::vector()); + MockPeerManager::return_value.get_peer_dims.side_effect = [] + (Reference const & peer_kernel) + { + if (peer_kernel == "other1") + return std::vector({20, 7}); + if (peer_kernel == "other") + return std::vector({25}); + if (peer_kernel == "other2") + return std::vector(); + throw std::runtime_error("Unexpected kernel in get_peer_dims"); + }; comm.connect(conduits, peer_dims, peer_locations); - ASSERT_EQ(MockPeerManager::last_constructed_conduits, conduits); - ASSERT_EQ(MockPeerManager::last_constructed_peer_dims, peer_dims); - ASSERT_EQ(MockPeerManager::last_constructed_peer_locations, peer_locations); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<2>(), conduits); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<3>(), peer_dims); + ASSERT_EQ(comm.peer_manager_->constructor.call_arg<4>(), peer_locations); auto const & ports = comm.ports_; @@ -372,75 +448,70 @@ TEST(libmuscle_communicator, test_connect_inferred_ports) { ASSERT_FALSE(ports.at("out3").is_vector()); } -TEST(libmuscle_communicator, send_message) { - reset_mocks(); +TEST_F(libmuscle_communicator, send_message) { auto comm = connected_communicator(); Message message(0.0, "test", Settings()); comm->send_message("out", message); - ASSERT_EQ(MockPostOffice::last_receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->sender, "kernel[13].out"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->timestamp, 0.0); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_EQ(MockPostOffice::last_message->data.as(), "test"); - ASSERT_TRUE(MockPostOffice::last_message->settings_overlay.is_a()); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "other.in[13]"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "kernel[13].out"); + ASSERT_EQ(sent_msg->receiver, "other.in[13]"); + ASSERT_EQ(sent_msg->timestamp, 0.0); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_EQ(sent_msg->data.as(), "test"); + ASSERT_TRUE(sent_msg->settings_overlay.is_a()); } -TEST(libmuscle_communicator, send_on_disconnected_port) { - reset_mocks(); - +TEST_F(libmuscle_communicator, send_on_disconnected_port) { auto comm = connected_communicator(); - MockPeerManager::is_connected_return_value = false; + comm->peer_manager_->is_connected.return_value = false; Message message(0.0, "test", Settings()); comm->send_message("not_connected", message); } -TEST(libmuscle_communicator, send_on_invalid_port) { - reset_mocks(); - +TEST_F(libmuscle_communicator, send_on_invalid_port) { auto comm = connected_communicator(); Message message(0.0, "test", Settings()); ASSERT_THROW(comm->send_message("[$Invalid_id", message), std::invalid_argument); } -TEST(libmuscle_communicator, send_msgpack) { - reset_mocks(); +TEST_F(libmuscle_communicator, send_msgpack) { auto comm = connected_communicator(); Message message(0.0, Data::dict("test", 17), Settings()); comm->send_message("out", message); - ASSERT_EQ(MockPostOffice::last_receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->sender, "kernel[13].out"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->timestamp, 0.0); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_EQ(MockPostOffice::last_message->data["test"].as(), 17); - ASSERT_TRUE(MockPostOffice::last_message->settings_overlay.is_a()); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "other.in[13]"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "kernel[13].out"); + ASSERT_EQ(sent_msg->receiver, "other.in[13]"); + ASSERT_EQ(sent_msg->timestamp, 0.0); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_EQ(sent_msg->data["test"].as(), 17); + ASSERT_TRUE(sent_msg->settings_overlay.is_a()); } -TEST(libmuscle_communicator, send_message_with_slot) { - reset_mocks(); +TEST_F(libmuscle_communicator, send_message_with_slot) { auto comm = connected_communicator2(); Message message(0.0, "test", Settings()); comm->send_message("out", message, 13); - ASSERT_EQ(MockPostOffice::last_receiver, "kernel[13].in"); - ASSERT_EQ(MockPostOffice::last_message->sender, "other.out[13]"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "kernel[13].in"); - ASSERT_EQ(MockPostOffice::last_message->timestamp, 0.0); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_EQ(MockPostOffice::last_message->data.as(), "test"); - ASSERT_TRUE(MockPostOffice::last_message->settings_overlay.is_a()); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "kernel[13].in"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "other.out[13]"); + ASSERT_EQ(sent_msg->receiver, "kernel[13].in"); + ASSERT_EQ(sent_msg->timestamp, 0.0); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_EQ(sent_msg->data.as(), "test"); + ASSERT_TRUE(sent_msg->settings_overlay.is_a()); } -TEST(libmuscle_communicator, send_message_resizable) { - reset_mocks(); +TEST_F(libmuscle_communicator, send_message_resizable) { auto comm = connected_communicator3(); Message message(0.0, "test", Settings()); @@ -449,18 +520,18 @@ TEST(libmuscle_communicator, send_message_resizable) { comm->get_port("out").set_length(20); comm->send_message("out", message, 13); - ASSERT_EQ(MockPostOffice::last_receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->sender, "kernel.out[13]"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->port_length.get(), 20); - ASSERT_EQ(MockPostOffice::last_message->timestamp, 0.0); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_EQ(MockPostOffice::last_message->data.as(), "test"); - ASSERT_TRUE(MockPostOffice::last_message->settings_overlay.is_a()); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "other.in[13]"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "kernel.out[13]"); + ASSERT_EQ(sent_msg->receiver, "other.in[13]"); + ASSERT_EQ(sent_msg->port_length.get(), 20); + ASSERT_EQ(sent_msg->timestamp, 0.0); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_EQ(sent_msg->data.as(), "test"); + ASSERT_TRUE(sent_msg->settings_overlay.is_a()); } -TEST(libmuscle_communicator, send_with_settings) { - reset_mocks(); +TEST_F(libmuscle_communicator, send_with_settings) { auto comm = connected_communicator(); Settings settings; @@ -468,18 +539,18 @@ TEST(libmuscle_communicator, send_with_settings) { Message message(0.0, "test", settings); comm->send_message("out", message); - ASSERT_EQ(MockPostOffice::last_receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->sender, "kernel[13].out"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->timestamp, 0.0); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_EQ(MockPostOffice::last_message->data.as(), "test"); - ASSERT_TRUE(MockPostOffice::last_message->settings_overlay.is_a()); - ASSERT_EQ(MockPostOffice::last_message->settings_overlay.as()["test2"], "testing"); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "other.in[13]"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "kernel[13].out"); + ASSERT_EQ(sent_msg->receiver, "other.in[13]"); + ASSERT_EQ(sent_msg->timestamp, 0.0); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_EQ(sent_msg->data.as(), "test"); + ASSERT_TRUE(sent_msg->settings_overlay.is_a()); + ASSERT_EQ(sent_msg->settings_overlay.as()["test2"], "testing"); } -TEST(libmuscle_communicator, send_settings) { - reset_mocks(); +TEST_F(libmuscle_communicator, send_settings) { auto comm = connected_communicator(); Settings settings; @@ -487,48 +558,45 @@ TEST(libmuscle_communicator, send_settings) { Message message(0.0, settings, Settings()); comm->send_message("out", message); - ASSERT_EQ(MockPostOffice::last_receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->sender, "kernel[13].out"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->timestamp, 0.0); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_TRUE(MockPostOffice::last_message->data.is_a()); - ASSERT_EQ(MockPostOffice::last_message->data.as()["test1"], "testing"); - ASSERT_TRUE(MockPostOffice::last_message->settings_overlay.is_a()); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "other.in[13]"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "kernel[13].out"); + ASSERT_EQ(sent_msg->receiver, "other.in[13]"); + ASSERT_EQ(sent_msg->timestamp, 0.0); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_TRUE(sent_msg->data.is_a()); + ASSERT_EQ(sent_msg->data.as()["test1"], "testing"); + ASSERT_TRUE(sent_msg->settings_overlay.is_a()); } -TEST(libmuscle_communicator, close_port) { - reset_mocks(); +TEST_F(libmuscle_communicator, close_port) { auto comm = connected_communicator(); comm->close_port("out"); - ASSERT_EQ(MockPostOffice::last_receiver, "other.in[13]"); - ASSERT_EQ(MockPostOffice::last_message->sender, "kernel[13].out"); - ASSERT_EQ(MockPostOffice::last_message->receiver, "other.in[13]"); - ASSERT_EQ( - MockPostOffice::last_message->timestamp, - std::numeric_limits::infinity()); - ASSERT_FALSE(MockPostOffice::last_message->next_timestamp.is_set()); - ASSERT_TRUE(libmuscle::_MUSCLE_IMPL_NS::is_close_port(MockPostOffice::last_message->data)); + ASSERT_EQ(comm->post_office_.deposit.call_arg<0>(), "other.in[13]"); + auto const & sent_msg = comm->post_office_.deposit.call_arg<1>(); + ASSERT_EQ(sent_msg->sender, "kernel[13].out"); + ASSERT_EQ(sent_msg->receiver, "other.in[13]"); + ASSERT_EQ(sent_msg->timestamp, std::numeric_limits::infinity()); + ASSERT_FALSE(sent_msg->next_timestamp.is_set()); + ASSERT_TRUE(::libmuscle::_MUSCLE_IMPL_NS::is_close_port(sent_msg->data)); } -TEST(libmuscle_communicator, receive_message) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "other.out[13]"; - MockMPPClient::next_receive_message.receiver = "kernel[13].in"; +TEST_F(libmuscle_communicator, receive_message) { + next_received_message.sender = "other.out[13]"; + next_received_message.receiver = "kernel[13].in"; auto comm = connected_communicator(); Message msg = comm->receive_message("in"); - ASSERT_EQ(MockMPPClient::last_receiver, "kernel[13].in"); + ASSERT_EQ(comm->clients_.at("other")->receive.call_arg<0>(), "kernel[13].in"); ASSERT_TRUE(msg.data().is_a_dict()); ASSERT_EQ(msg.data()["test1"].as(), 12); } -TEST(libmuscle_communicator, receive_message_default) { - reset_mocks(); - MockPeerManager::is_connected_return_value = false; +TEST_F(libmuscle_communicator, receive_message_default) { + MockPeerManager::return_value.is_connected.return_value = false; Message default_msg(3.0, 4.0, "test"); auto comm = connected_communicator(); @@ -540,79 +608,71 @@ TEST(libmuscle_communicator, receive_message_default) { ASSERT_FALSE(msg.has_settings()); } -TEST(libmuscle_communicator, receive_message_no_default) { - reset_mocks(); - MockPeerManager::is_connected_return_value = false; +TEST_F(libmuscle_communicator, receive_message_no_default) { + MockPeerManager::return_value.is_connected.return_value = false; auto comm = connected_communicator(); ASSERT_THROW(comm->receive_message("not_connected"), std::runtime_error); } -TEST(libmuscle_communicator, receive_on_invalid_port) { - reset_mocks(); - +TEST_F(libmuscle_communicator, receive_on_invalid_port) { auto comm = connected_communicator(); ASSERT_THROW(comm->receive_message("@$Invalid_id"), std::invalid_argument); } -TEST(libmuscle_communicator, receive_message_with_slot) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "kernel[13].out"; - MockMPPClient::next_receive_message.receiver = "other.in[13]"; +TEST_F(libmuscle_communicator, receive_message_with_slot) { + next_received_message.sender = "kernel[13].out"; + next_received_message.receiver = "other.in[13]"; auto comm = connected_communicator2(); Message msg = comm->receive_message("in", 13); - ASSERT_EQ(MockMPPClient::last_receiver, "other.in[13]"); + ASSERT_EQ(comm->clients_.at("kernel[13]")->receive.call_arg<0>(), "other.in[13]"); ASSERT_TRUE(msg.data().is_a_dict()); ASSERT_EQ(msg.data()["test1"].as(), 12); } -TEST(libmuscle_communicator, receive_message_resizable) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "other.out[13]"; - MockMPPClient::next_receive_message.receiver = "kernel.in[13]"; - MockMPPClient::next_receive_message.port_length = 20; +TEST_F(libmuscle_communicator, receive_message_resizable) { + next_received_message.sender = "other.out[13]"; + next_received_message.receiver = "kernel.in[13]"; + next_received_message.port_length = 20; auto comm = connected_communicator3(); Message msg = comm->receive_message("in", 13); - ASSERT_EQ(MockMPPClient::last_receiver, "kernel.in[13]"); + ASSERT_EQ(comm->clients_.at("other")->receive.call_arg<0>(), "kernel.in[13]"); ASSERT_TRUE(msg.data().is_a_dict()); ASSERT_EQ(msg.data()["test1"].as(), 12); ASSERT_EQ(comm->get_port("in").get_length(), 20); } -TEST(libmuscle_communicator, receive_with_settings) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "other.out[13]"; - MockMPPClient::next_receive_message.receiver = "kernel[13].in"; +TEST_F(libmuscle_communicator, receive_with_settings) { + next_received_message.sender = "other.out[13]"; + next_received_message.receiver = "kernel[13].in"; auto comm = connected_communicator(); Message msg = comm->receive_message("in"); - ASSERT_EQ(MockMPPClient::last_receiver, "kernel[13].in"); + ASSERT_EQ(comm->clients_.at("other")->receive.call_arg<0>(), "kernel[13].in"); ASSERT_TRUE(msg.data().is_a_dict()); ASSERT_EQ(msg.data()["test1"].as(), 12); - ASSERT_EQ(msg.settings().at("test2"), 3.1); + ASSERT_EQ(msg.settings().at("test2").as(), 3.1); } -TEST(libmuscle_communicator, receive_message_with_slot_and_settings) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "kernel[13].out"; - MockMPPClient::next_receive_message.receiver = "other.in[13]"; +TEST_F(libmuscle_communicator, receive_message_with_slot_and_settings) { + next_received_message.sender = "kernel[13].out"; + next_received_message.receiver = "other.in[13]"; auto comm = connected_communicator2(); Message msg = comm->receive_message("in", 13); - ASSERT_EQ(MockMPPClient::last_receiver, "other.in[13]"); + ASSERT_EQ(comm->clients_.at("kernel[13]")->receive.call_arg<0>(), "other.in[13]"); ASSERT_TRUE(msg.data().is_a_dict()); ASSERT_EQ(msg.data()["test1"].as(), 12); ASSERT_EQ(msg.settings().at("test2"), 3.1); } -TEST(libmuscle_communicator, port_message_counts) { - reset_mocks(); +TEST_F(libmuscle_communicator, port_message_counts) { auto comm = connected_communicator(); Message message(0.0, "test", Settings()); @@ -640,8 +700,7 @@ TEST(libmuscle_communicator, port_message_counts) { std::runtime_error); } -TEST(libmuscle_communicator, vector_port_message_counts) { - reset_mocks(); +TEST_F(libmuscle_communicator, vector_port_message_counts) { auto comm = connected_communicator2(); auto msg_counts = comm->get_message_counts(); @@ -675,10 +734,9 @@ TEST(libmuscle_communicator, vector_port_message_counts) { ASSERT_EQ(msg_counts["out"], expected_counts); } -TEST(libmuscle_communicator, port_count_validation) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "other.out[13]"; - MockMPPClient::next_receive_message.receiver = "kernel[13].in"; +TEST_F(libmuscle_communicator, port_count_validation) { + next_received_message.sender = "other.out[13]"; + next_received_message.receiver = "kernel[13].in"; auto comm = connected_communicator(); Message msg = comm->receive_message("in"); @@ -689,11 +747,10 @@ TEST(libmuscle_communicator, port_count_validation) { ASSERT_THROW(comm->receive_message("in"), std::runtime_error); } -TEST(libmuscle_communicator, port_discard_error_on_resume) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "other.out[13]"; - MockMPPClient::next_receive_message.receiver = "kernel[13].in"; - MockMPPClient::next_receive_message.message_number = 1; +TEST_F(libmuscle_communicator, port_discard_error_on_resume) { + next_received_message.sender = "other.out[13]"; + next_received_message.receiver = "kernel[13].in"; + next_received_message.message_number = 1; auto comm = connected_communicator(); @@ -713,16 +770,26 @@ TEST(libmuscle_communicator, port_discard_error_on_resume) { // TODO: test that a debug message was logged? } -TEST(libmuscle_communicator, port_discard_success_on_resume) { - reset_mocks(); - MockMPPClient::next_receive_message.sender = "other.out[13]"; - MockMPPClient::next_receive_message.receiver = "kernel[13].in"; - MockMPPClient::next_receive_message.message_number = 1; - MockMPPClient::next_receive_message.timestamp = 1.0; - MockMPPClient::side_effect = [](){ +TEST_F(libmuscle_communicator, port_discard_success_on_resume) { + next_received_message.sender = "other.out[13]"; + next_received_message.receiver = "kernel[13].in"; + next_received_message.message_number = 1; + next_received_message.timestamp = 1.0; + + auto & ret_val = MockMPPClient::return_value; + + using RecvRet = std::tuple< + DataConstRef, std::tuple< + ProfileTimestamp, ProfileTimestamp, ProfileTimestamp>>; + + ret_val.receive.side_effect = [this](Reference const &) -> RecvRet { // ensure message_number increases after every receive() - MockMPPClient::next_receive_message.message_number ++; - MockMPPClient::next_receive_message.timestamp += 1.0; + next_received_message.message_number++; + next_received_message.timestamp += 1.0; + return std::make_tuple( + next_received_message.encoded(), std::make_tuple( + ProfileTimestamp(1.0), ProfileTimestamp(2.0), + ProfileTimestamp(3.0))); }; auto comm = connected_communicator(); diff --git a/libmuscle/cpp/src/libmuscle/tests/test_instance.cpp b/libmuscle/cpp/src/libmuscle/tests/test_instance.cpp index 6d1e8f35..5dc69f45 100644 --- a/libmuscle/cpp/src/libmuscle/tests/test_instance.cpp +++ b/libmuscle/cpp/src/libmuscle/tests/test_instance.cpp @@ -4,7 +4,7 @@ #define LIBMUSCLE_MOCK_MMP_CLIENT #define LIBMUSCLE_MOCK_PROFILER -// into the real implementation, +// into the real implementation to test. #include #include @@ -18,12 +18,6 @@ #include #include -// then add mock implementations as needed. -#include -#include -#include -#include - // Test code dependencies #include #include @@ -40,8 +34,8 @@ using libmuscle::_MUSCLE_IMPL_NS::ClosePort; using libmuscle::_MUSCLE_IMPL_NS::Instance; using libmuscle::_MUSCLE_IMPL_NS::InstanceFlags; using libmuscle::_MUSCLE_IMPL_NS::Message; -using libmuscle::_MUSCLE_IMPL_NS::MockCommunicator; -using libmuscle::_MUSCLE_IMPL_NS::MockMMPClient; +using libmuscle::_MUSCLE_IMPL_NS::Optional; +using libmuscle::_MUSCLE_IMPL_NS::Port; using libmuscle::_MUSCLE_IMPL_NS::PortsDescription; using ymmsl::Reference; @@ -53,15 +47,6 @@ int main(int argc, char *argv[]) { } -/* Mocks have internal state, which needs to be reset before each test. This - * means that the tests are not reentrant, and cannot be run in parallel. - * It's all fast enough, so that's not a problem. - */ -void reset_mocks() { - MockCommunicator::reset(); - MockMMPClient::reset(); -} - std::vector test_argv() { char const * arg0 = "\0"; char const * arg1 = "--muscle-instance=test_instance[13][42]"; @@ -70,9 +55,16 @@ std::vector test_argv() { } -TEST(libmuscle_instance, create_instance) { - reset_mocks(); +class libmuscle_instance : public ::testing::Test { + RESET_MOCKS( + ::libmuscle::_MUSCLE_IMPL_NS::MockCommunicator, + ::libmuscle::_MUSCLE_IMPL_NS::MockLogger, + ::libmuscle::_MUSCLE_IMPL_NS::MockMMPClient, + ::libmuscle::_MUSCLE_IMPL_NS::MockProfiler); +}; + +TEST_F(libmuscle_instance, create_instance) { auto argv = test_argv(); Instance instance(argv.size(), argv.data(), PortsDescription({ @@ -81,13 +73,19 @@ TEST(libmuscle_instance, create_instance) { })); ASSERT_EQ(instance.impl_()->instance_name_, "test_instance[13][42]"); - ASSERT_EQ(MockMMPClient::num_constructed, 1); - ASSERT_EQ(MockMMPClient::last_instance_id, "test_instance[13][42]"); - ASSERT_EQ(MockMMPClient::last_location, "node042:9000"); - ASSERT_EQ(MockCommunicator::num_constructed, 1); - ASSERT_EQ(MockMMPClient::last_registered_locations.at(0), "tcp:test1,test2"); - ASSERT_EQ(MockMMPClient::last_registered_locations.at(1), "tcp:test3"); - ASSERT_EQ(MockMMPClient::last_registered_ports.size(), 3); + + auto const & constructor = instance.impl_()->manager_->constructor; + ASSERT_TRUE(constructor.called_once()); + ASSERT_EQ(constructor.call_arg<0>(), "test_instance[13][42]"); + ASSERT_EQ(constructor.call_arg<1>(), "node042:9000"); + + ASSERT_TRUE(instance.impl_()->communicator_->constructor.called_once()); + + auto const & register_instance = instance.impl_()->manager_->register_instance; + ASSERT_EQ(register_instance.call_arg<0>().at(0), "tcp:test1,test2"); + ASSERT_EQ(register_instance.call_arg<0>().at(1), "tcp:test3"); + ASSERT_EQ(register_instance.call_arg<1>().size(), 3); + auto & settings = instance.impl_()->settings_manager_; ASSERT_EQ(settings.base["test_int"], 10); ASSERT_EQ(settings.base["test_string"], "testing"); @@ -95,46 +93,46 @@ TEST(libmuscle_instance, create_instance) { } -TEST(libmuscle_instance, send) { - reset_mocks(); +TEST_F(libmuscle_instance, send) { auto argv = test_argv(); Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::O_F, {"out"}} })); - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.list_ports.return_value = PortsDescription({ {Operator::O_F, {"out"}} }); - MockCommunicator::get_port_return_value.emplace( - "out", Port("out", Operator::O_F, false, true, 0, {})); + communicator.port_exists.return_value = true; + Port out_port("out", Operator::O_F, false, true, 0, {}); + communicator.get_port.return_value = &out_port; Message msg(3.0, 4.0, "Testing"); instance.send("out", msg); - ASSERT_EQ(MockCommunicator::last_sent_port, "out"); - ASSERT_FALSE(MockCommunicator::last_sent_slot.is_set()); - ASSERT_EQ(MockCommunicator::last_sent_message.timestamp(), 3.0); - ASSERT_EQ(MockCommunicator::last_sent_message.next_timestamp(), 4.0); - ASSERT_EQ(MockCommunicator::last_sent_message.data().as(), "Testing"); + ASSERT_EQ(communicator.send_message.call_arg<0>(), "out"); + ASSERT_FALSE(communicator.send_message.call_arg<2>().is_set()); + auto const & sent_msg = communicator.send_message.call_arg<1>(); + ASSERT_EQ(sent_msg.timestamp(), 3.0); + ASSERT_EQ(sent_msg.next_timestamp(), 4.0); + ASSERT_EQ(sent_msg.data().as(), "Testing"); } -TEST(libmuscle_instance, send_invalid_port) { - reset_mocks(); +TEST_F(libmuscle_instance, send_invalid_port) { auto argv = test_argv(); Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::O_F, {"out"}} })); - MockCommunicator::port_exists_return_value = false; + instance.impl_()->communicator_->port_exists.return_value = false; Message msg(3.0, 4.0, "Testing"); ASSERT_THROW(instance.send("out", msg), std::logic_error); } -TEST(libmuscle_instance, get_setting) { - reset_mocks(); +TEST_F(libmuscle_instance, get_setting) { auto argv = test_argv(); Instance instance(argv.size(), argv.data()); @@ -166,21 +164,24 @@ TEST(libmuscle_instance, get_setting) { ASSERT_THROW(instance.get_setting_as("test1"), std::bad_cast); } -TEST(libmuscle_instance, receive) { - reset_mocks(); +TEST_F(libmuscle_instance, receive) { auto argv = test_argv(); + Port in_port("in", Operator::S, false, true, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::S, {"in"}} })); - MockCommunicator::list_ports_return_value = PortsDescription({ - {Operator::S, {"in"}} - }); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::S, false, true, 0, {})); - MockCommunicator::next_received_message["in"] = - std::make_unique(1.0, 2.0, "Testing receive", Settings()); + + auto & communicator = *instance.impl_()->communicator_; + communicator.list_ports.return_value = PortsDescription({ + {Operator::S, {"in"}} + }); + communicator.port_exists.return_value = true; + communicator.get_port.return_value = &in_port; + communicator.receive_message.return_value = Message( + 1.0, 2.0, "Testing receive", Settings()); Message msg(instance.receive("in")); @@ -190,26 +191,31 @@ TEST(libmuscle_instance, receive) { ASSERT_TRUE(msg.data().is_a()); ASSERT_EQ(msg.data().as(), "Testing receive"); - // make sure Instance shuts down cleanly - MockCommunicator::next_received_message["in"] = - std::make_unique(0.0, ClosePort(), Settings()); + // Make sure Instance shuts down cleanly + // in_port will be gone already because it was created after instance, but it will + // still be returned by get_port, and then we crash. If the instance thinks that + // there are no ports, then it won't try to close them either. + communicator.list_ports.return_value = PortsDescription(); } -TEST(libmuscle_instance, receive_f_init) { - reset_mocks(); +TEST_F(libmuscle_instance, receive_f_init) { auto argv = test_argv(); + Port in_port("in", Operator::F_INIT, false, true, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::F_INIT, {"in"}} })); - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.settings_in_connected.return_value = false; + communicator.port_exists.return_value = true; + communicator.list_ports.return_value = PortsDescription({ {Operator::F_INIT, {"in"}} }); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::F_INIT, false, true, 0, {})); - MockCommunicator::next_received_message["in"] = - std::make_unique(1.0, 2.0, "Testing receive", Settings()); + communicator.get_port.return_value = &in_port; + communicator.receive_message.return_value = Message( + 1.0, 2.0, "Testing receive", Settings()); ASSERT_TRUE(instance.reuse_instance()); Message msg(instance.receive("in")); @@ -222,26 +228,31 @@ TEST(libmuscle_instance, receive_f_init) { Port port("in", Operator::F_INIT, false, true, 0, {}); port.set_closed(); - MockCommunicator::get_port_return_value.at("in") = port; + communicator.get_port.return_value = &port; ASSERT_THROW(instance.receive("in"), std::logic_error); + + // Make sure Instance shuts down cleanly (see receive above) + communicator.list_ports.return_value = PortsDescription(); } -TEST(libmuscle_instance, receive_default) { - reset_mocks(); +TEST_F(libmuscle_instance, receive_default) { auto argv = test_argv(); + Port in_port("in", Operator::S, false, false, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::S, {"in"}} })); - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.list_ports.return_value = PortsDescription({ {Operator::S, {"in"}} }); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::S, false, false, 0, {})); + communicator.port_exists.return_value = true; + communicator.get_port.return_value = &in_port; Message default_msg(1.0, 2.0, "Testing receive"); - + communicator.receive_message.return_value = default_msg; Message msg(instance.receive("in", default_msg)); ASSERT_EQ(msg.timestamp(), 1.0); @@ -249,45 +260,55 @@ TEST(libmuscle_instance, receive_default) { ASSERT_EQ(msg.next_timestamp(), 2.0); ASSERT_TRUE(msg.data().is_a()); ASSERT_EQ(msg.data().as(), "Testing receive"); - ASSERT_THROW(instance.receive("not_connected"), std::logic_error); + + // Make sure Instance shuts down cleanly (see receive above) + communicator.list_ports.return_value = PortsDescription(); } -TEST(libmuscle_instance, receive_invalid_port) { - reset_mocks(); +TEST_F(libmuscle_instance, receive_invalid_port) { auto argv = test_argv(); + Port in_port("in", Operator::S, false, false, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::S, {"in"}} })); - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.list_ports.return_value = PortsDescription({ {Operator::S, {"in"}} }); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::S, false, false, 0, {})); + communicator.port_exists.return_value = false; + communicator.get_port.return_value = &in_port; ASSERT_THROW(instance.receive("does_not_exist", 1), std::logic_error); + + // Make sure Instance shuts down cleanly (see receive above) + communicator.list_ports.return_value = PortsDescription({}); } -TEST(libmuscle_instance, receive_with_settings) { - reset_mocks(); +TEST_F(libmuscle_instance, receive_with_settings) { auto argv = test_argv(); + Port in_port("in", Operator::F_INIT, false, true, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::F_INIT, {"in"}} }), InstanceFlags::DONT_APPLY_OVERLAY); - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.settings_in_connected.return_value = false; + communicator.list_ports.return_value = PortsDescription({ {Operator::F_INIT, {"in"}} }); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::F_INIT, false, true, 0, {})); + communicator.port_exists.return_value = true; + communicator.get_port.return_value = &in_port; Settings recv_settings; recv_settings["test1"] = 12; - MockCommunicator::next_received_message["in"] = - std::make_unique(1.0, "Testing with settings", recv_settings); + communicator.receive_message.return_value = Message( + 1.0, "Testing with settings", recv_settings); ASSERT_TRUE(instance.reuse_instance()); Message msg(instance.receive_with_settings("in")); @@ -299,53 +320,68 @@ TEST(libmuscle_instance, receive_with_settings) { ASSERT_TRUE(msg.has_settings()); ASSERT_EQ(msg.settings().at("test1"), 12); - // make sure Instance shuts down cleanly - MockCommunicator::next_received_message["in"] = - std::make_unique(0.0, ClosePort(), Settings()); + // Make sure Instance shuts down cleanly (see receive above) + communicator.list_ports.return_value = PortsDescription({}); } -TEST(libmuscle_instance, receive_parallel_universe) { - reset_mocks(); +TEST_F(libmuscle_instance, receive_parallel_universe) { auto argv = test_argv(); + Port port("in", Operator::F_INIT, false, true, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::F_INIT, {"in"}} })); - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.list_ports.return_value = PortsDescription({ {Operator::F_INIT, {"in"}} }); - Port port("in", Operator::F_INIT, false, true, 0, {}); port.set_closed(); - MockCommunicator::get_port_return_value.emplace("in", port); - - MockCommunicator::settings_in_connected_return_value = true; - - Settings recv_settings; - recv_settings["test1"] = 12; - MockCommunicator::next_received_message["in"] = - std::make_unique(1.0, "Testing", recv_settings); - recv_settings["test2"] = "test"; - MockCommunicator::next_received_message["muscle_settings_in"] = - std::make_unique(1.0, recv_settings, Settings()); + communicator.get_port.return_value = &port; + + communicator.settings_in_connected.return_value = true; + + communicator.receive_message.side_effect = []( + std::string const & port_name, Optional slot, + Optional const & default_msg) -> Message + { + Settings recv_settings; + if (port_name == "in") { + recv_settings["test1"] = 12; + return Message(1.0, "Testing", recv_settings); + } + if (port_name == "muscle_settings_in") { + recv_settings["test2"] = "test"; + return Message(1.0, "Testing", recv_settings); + } + throw std::runtime_error("Unexpected port name in receive_parallel_universe"); + }; ASSERT_THROW(instance.reuse_instance(), std::logic_error); + + // Make sure Instance shuts down cleanly (see receive above) + communicator.list_ports.return_value = PortsDescription({}); } -TEST(libmuscle_instance, receive_with_settings_default) { - reset_mocks(); +TEST_F(libmuscle_instance, receive_with_settings_default) { auto argv = test_argv(); + Port in_port("in", Operator::F_INIT, false, false, 0, {}); + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::F_INIT, {"not_connected"}} }), InstanceFlags::DONT_APPLY_OVERLAY); - MockCommunicator::list_ports_return_value = PortsDescription({ - {Operator::F_INIT, {"not_connected"}} - }); - MockCommunicator::get_port_return_value.emplace( - "not_connected", Port("in", Operator::F_INIT, false, false, 0, {})); + auto & communicator = *instance.impl_()->communicator_; + communicator.settings_in_connected.return_value = false; + communicator.list_ports.return_value = PortsDescription({ + {Operator::F_INIT, {"not_connected"}} + }); + + communicator.port_exists.return_value = true; + communicator.get_port.return_value = &in_port; Settings default_settings; default_settings["test1"] = 12; @@ -363,8 +399,7 @@ TEST(libmuscle_instance, receive_with_settings_default) { } -TEST(libmuscle_instance, reuse_instance_receive_overlay) { - reset_mocks(); +TEST_F(libmuscle_instance, reuse_instance_receive_overlay) { auto argv = test_argv(); Instance instance(argv.size(), argv.data(), PortsDescription({ @@ -378,9 +413,10 @@ TEST(libmuscle_instance, reuse_instance_receive_overlay) { Settings test_overlay; test_overlay["test2"] = "abc"; - MockCommunicator::settings_in_connected_return_value = true; - MockCommunicator::next_received_message["muscle_settings_in"] = - std::make_unique(0.0, test_overlay, test_base_settings); + auto & communicator = *instance.impl_()->communicator_; + communicator.settings_in_connected.return_value = true; + communicator.receive_message.return_value = Message( + 0.0, test_overlay, test_base_settings); instance.reuse_instance(); @@ -389,64 +425,96 @@ TEST(libmuscle_instance, reuse_instance_receive_overlay) { ASSERT_EQ(instance.impl_()->settings_manager_.overlay.at("test2"), "abc"); } -TEST(libmuscle_instance, reuse_instance_closed_port) { - reset_mocks(); +TEST_F(libmuscle_instance, reuse_instance_closed_port) { auto argv = test_argv(); + std::unordered_map ports = { + {"not_connected", Port("not_connected", Operator::F_INIT, false, false, 0, {})}, + {"in", Port("in", Operator::F_INIT, false, true, 0, {})}, + {"out", Port("out", Operator::O_F, false, true, 0, {})}}; + Instance instance(argv.size(), argv.data(), PortsDescription({ {Operator::F_INIT, {"in", "not_connected"}}, {Operator::O_F, {"out"}} })); - MockCommunicator::settings_in_connected_return_value = true; - MockCommunicator::next_received_message["muscle_settings_in"] = - std::make_unique(0.0, Settings(), Settings()); - MockCommunicator::next_received_message["in"] = - std::make_unique(0.0, ClosePort(), Settings()); - - MockCommunicator::list_ports_return_value = PortsDescription({ + auto & communicator = *instance.impl_()->communicator_; + communicator.settings_in_connected.return_value = true; + communicator.receive_message.side_effect = [&ports]( + std::string const & port_name, Optional slot, + Optional const & default_msg) -> Message + { + if (port_name == "in") { + ports.at(port_name).set_closed(); + return Message(0.0, ClosePort(), Settings()); + } + if (port_name == "muscle_settings_in") { + return Message(0.0, Settings(), Settings()); + } + throw std::runtime_error("Unexpected port name in reuse_instance_closed_port"); + }; + + communicator.list_ports.return_value = PortsDescription({ {Operator::F_INIT, {"in", "not_connected"}}, {Operator::O_F, {"out"}} }); - MockCommunicator::get_port_return_value.emplace( - "not_connected", - Port("not_connected", Operator::F_INIT, false, false, 0, {})); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::F_INIT, false, true, 0, {})); - MockCommunicator::get_port_return_value.emplace( - "out", Port("out", Operator::O_F, false, true, 0, {})); + communicator.get_port.side_effect = [&ports] + (std::string const & port_name) -> Port & + { + return ports.at(port_name); + }; ASSERT_FALSE(instance.reuse_instance()); + + // Make sure Instance shuts down cleanly (see receive above) + communicator.settings_in_connected.return_value = false; + communicator.list_ports.return_value = PortsDescription({}); } -TEST(libmuscle_instance, reuse_instance_vector_port) { - reset_mocks(); +TEST_F(libmuscle_instance, reuse_instance_vector_port) { auto argv = test_argv(); - Instance instance(argv.size(), argv.data(), - PortsDescription({ - {Operator::F_INIT, {"in[]"}} - })); + Port in_port("in", Operator::F_INIT, true, true, 0, {10}); - MockCommunicator::settings_in_connected_return_value = true; - MockCommunicator::next_received_message["muscle_settings_in"] = - std::make_unique(0.0, Settings(), Settings()); + std::unordered_map messages = { + {"muscle_settings_in", Message(0.0, Settings(), Settings())}}; for (int i = 0; i < 10; ++i) { Reference port_slot("in"); port_slot += i; std::ostringstream oss; oss << "test " << i; - MockCommunicator::next_received_message[port_slot] = - std::make_unique(0.0, oss.str(), Settings()); + messages.insert({port_slot, Message(0.0, oss.str(), Settings())}); } - MockCommunicator::list_ports_return_value = PortsDescription({ + Instance instance(argv.size(), argv.data(), + PortsDescription({ + {Operator::F_INIT, {"in[]"}} + })); + + auto & communicator = *instance.impl_()->communicator_; + + communicator.settings_in_connected.return_value = true; + + communicator.receive_message.side_effect = [&messages, &in_port]( + std::string const & port, Optional slot, + Optional const & default_msg) + { + Reference port_slot(port); + if (slot.is_set()) + port_slot += slot.get(); + auto const & msg = messages.at(port_slot); + if (is_close_port(msg.data())) + in_port.set_closed(slot.get()); + return msg; + }; + + communicator.list_ports.return_value = PortsDescription({ {Operator::F_INIT, {"in"}} }); - MockCommunicator::get_port_return_value.emplace( - "in", Port("in", Operator::F_INIT, true, true, 0, {10})); + communicator.port_exists.return_value = true; + communicator.get_port.return_value = &in_port; ASSERT_TRUE(instance.reuse_instance()); @@ -456,21 +524,19 @@ TEST(libmuscle_instance, reuse_instance_vector_port) { ASSERT_EQ(msg.data().as(), "test 5"); // make sure Instance shuts down cleanly + messages.clear(); for (int i = 0; i < 10; ++i) { Reference port_slot("in"); port_slot += i; - MockCommunicator::next_received_message[port_slot] = - std::make_unique(0.0, ClosePort(), Settings()); + messages.insert({port_slot, Message(0.0, ClosePort(), Settings())}); } } -TEST(libmuscle_instance, reuse_instance_no_f_init_ports) { - reset_mocks(); +TEST_F(libmuscle_instance, reuse_instance_no_f_init_ports) { auto argv = test_argv(); - Instance instance(argv.size(), argv.data(), - PortsDescription({})); + Instance instance(argv.size(), argv.data(), PortsDescription({})); - MockCommunicator::settings_in_connected_return_value = false; + instance.impl_()->communicator_->settings_in_connected.return_value = false; ASSERT_TRUE(instance.reuse_instance()); ASSERT_FALSE(instance.reuse_instance()); diff --git a/libmuscle/cpp/src/libmuscle/tests/test_logger.cpp b/libmuscle/cpp/src/libmuscle/tests/test_logger.cpp index 7aff2e6b..81395a2e 100644 --- a/libmuscle/cpp/src/libmuscle/tests/test_logger.cpp +++ b/libmuscle/cpp/src/libmuscle/tests/test_logger.cpp @@ -7,10 +7,10 @@ #include // then add mock implementations as needed. -#include - +#include // Test code dependencies +#include #include #include #include @@ -29,63 +29,60 @@ int main(int argc, char *argv[]) { return RUN_ALL_TESTS(); } -/* Mocks have internal state, which needs to be reset before each test. This - * means that the tests are not reentrant, and cannot be run in parallel. - * It's all fast enough, so that's not a problem. - */ -void reset_mocks() { - MockMMPClient::reset(); -} -TEST(libmuscle_logging, test_logger) { - reset_mocks(); - MockMMPClient manager(Reference("test_instance[10]"), ""); - Logger logger("test_instance[10]", "", manager); +struct libmuscle_logging : ::testing::Test { + RESET_MOCKS(MockMMPClient); + MockMMPClient mock_mmp_client_; +}; + + +TEST_F(libmuscle_logging, test_logger) { + Logger logger("test_instance[10]", "", mock_mmp_client_); logger.log(LogLevel::CRITICAL, "Testing: ", 10, " == ", 10.0); - auto const & msg = MockMMPClient::last_submitted_log_message; + auto const & msg = mock_mmp_client_.submit_log_message.call_arg<0>(); ASSERT_EQ(msg.instance_id, "test_instance[10]"); ASSERT_GT(msg.timestamp.seconds, 0.0); ASSERT_EQ(msg.level, LogLevel::CRITICAL); ASSERT_EQ(msg.text, "Testing: 10 == 10"); } -TEST(libmuscle_logging, test_set_level) { - reset_mocks(); - MockMMPClient manager(Reference("test_instance"), ""); - Logger logger("test_instance", "", manager); +TEST_F(libmuscle_logging, test_set_level) { + Logger logger("test_instance", "", mock_mmp_client_); + + auto const & submit = mock_mmp_client_.submit_log_message; // default is WARNING logger.log(LogLevel::WARNING, "WARNING"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "WARNING"); + ASSERT_EQ(submit.call_arg<0>().text, "WARNING"); logger.log(LogLevel::INFO, "INFO"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "WARNING"); + ASSERT_EQ(submit.call_arg<0>().text, "WARNING"); logger.log(LogLevel::WARNING, "WARNING2"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "WARNING2"); + ASSERT_EQ(submit.call_arg<0>().text, "WARNING2"); logger.log(LogLevel::DEBUG, "DEBUG"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "WARNING2"); + ASSERT_EQ(submit.call_arg<0>().text, "WARNING2"); logger.log(LogLevel::CRITICAL, "CRITICAL"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "CRITICAL"); + ASSERT_EQ(submit.call_arg<0>().text, "CRITICAL"); logger.set_remote_level(LogLevel::DEBUG); logger.log(LogLevel::DEBUG, "DEBUG"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "DEBUG"); + ASSERT_EQ(submit.call_arg<0>().text, "DEBUG"); logger.log(LogLevel::CRITICAL, "CRITICAL"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "CRITICAL"); + ASSERT_EQ(submit.call_arg<0>().text, "CRITICAL"); logger.set_remote_level(LogLevel::CRITICAL); logger.log(LogLevel::ERROR, "ERROR"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "CRITICAL"); + ASSERT_EQ(submit.call_arg<0>().text, "CRITICAL"); logger.log(LogLevel::CRITICAL, "CRITICAL2"); - ASSERT_EQ(MockMMPClient::last_submitted_log_message.text, "CRITICAL2"); + ASSERT_EQ(submit.call_arg<0>().text, "CRITICAL2"); } diff --git a/libmuscle/cpp/src/libmuscle/tests/test_profiler.cpp b/libmuscle/cpp/src/libmuscle/tests/test_profiler.cpp index f012e5ca..b531cdbc 100644 --- a/libmuscle/cpp/src/libmuscle/tests/test_profiler.cpp +++ b/libmuscle/cpp/src/libmuscle/tests/test_profiler.cpp @@ -9,9 +9,10 @@ #include // then add mock implementations as needed. -#include +#include -// and patch the background thread communication interval +// and provide the background thread communication interval function +// patched in above. #include using std::chrono_literals::operator""ms; @@ -24,6 +25,7 @@ std::chrono::steady_clock::duration communication_interval_() { // Test code dependencies +#include #include #include #include @@ -112,19 +114,14 @@ ProfileEvent get_queued_event( } -/* Mocks have internal state, which needs to be reset before each test. This - * means that the tests are not reentrant, and cannot be run in parallel. - * It's all fast enough, so that's not a problem. - */ -void reset_mocks() { - MockMMPClient::reset(); -} +struct libmuscle_profiler : ::testing::Test { + RESET_MOCKS(MockMMPClient); + MockMMPClient mock_mmp_client_; +}; -TEST(libmuscle_profiler, test_recording_events) { - reset_mocks(); - MockMMPClient mock_mmp_client(Reference("test_instance[10]"), ""); - Profiler profiler(mock_mmp_client); +TEST_F(libmuscle_profiler, test_recording_events) { + Profiler profiler(mock_mmp_client_); ProfileTimestamp t1, t2; ProfileEvent e(ProfileEventType::register_, t1, t2); @@ -137,10 +134,8 @@ TEST(libmuscle_profiler, test_recording_events) { } -TEST(libmuscle_profiler, test_disabling) { - reset_mocks(); - MockMMPClient mock_mmp_client(Reference("test_instance[10]"), ""); - Profiler profiler(mock_mmp_client); +TEST_F(libmuscle_profiler, test_disabling) { + Profiler profiler(mock_mmp_client_); profiler.set_level("none"); ProfileTimestamp t1, t2; @@ -154,10 +149,8 @@ TEST(libmuscle_profiler, test_disabling) { } -TEST(libmuscle_profiler, test_auto_stop_time) { - reset_mocks(); - MockMMPClient mock_mmp_client(Reference("test_instance[10]"), ""); - Profiler profiler(mock_mmp_client); +TEST_F(libmuscle_profiler, test_auto_stop_time) { + Profiler profiler(mock_mmp_client_); ProfileTimestamp t1; ProfileEvent e(ProfileEventType::send, t1); @@ -177,10 +170,8 @@ TEST(libmuscle_profiler, test_auto_stop_time) { ASSERT_LT(e2.start_time.get(), e2.stop_time.get()); } -TEST(libmuscle_profiler, test_send_to_mock_mmp_client) { - reset_mocks(); - MockMMPClient mock_mmp_client(Reference("test_instance[10]"), ""); - Profiler profiler(mock_mmp_client); +TEST_F(libmuscle_profiler, test_send_to_mock_mmp_client) { + Profiler profiler(mock_mmp_client_); ProfileEvent e1( ProfileEventType::receive, ProfileTimestamp(), ProfileTimestamp()); @@ -192,20 +183,20 @@ TEST(libmuscle_profiler, test_send_to_mock_mmp_client) { profiler.record_event(std::move(e)); } - ASSERT_EQ(mock_mmp_client.last_submitted_profile_events.size(), 0u); + ASSERT_FALSE(mock_mmp_client_.submit_profile_events.called()); ProfileEvent e2( ProfileEventType::receive_transfer, ProfileTimestamp(), ProfileTimestamp()); profiler.record_event(ProfileEvent(e2)); - ASSERT_EQ(mock_mmp_client.last_submitted_profile_events.size(), 10000u); - ASSERT_EQ(mock_mmp_client.last_submitted_profile_events.at(0), e1); - ASSERT_EQ(mock_mmp_client.last_submitted_profile_events.at(9999), e2); + ASSERT_TRUE(mock_mmp_client_.submit_profile_events.called()); + auto const & events = mock_mmp_client_.submit_profile_events.call_arg<0>(); + ASSERT_EQ(events.size(), 10000u); + ASSERT_EQ(events.at(0), e1); + ASSERT_EQ(events.at(9999), e2); } -TEST(libmuscle_profiler, test_send_timeout) { - reset_mocks(); - +TEST_F(libmuscle_profiler, test_send_timeout) { std::chrono::steady_clock::duration wait_time; if (getenv("CI")) { @@ -217,8 +208,7 @@ TEST(libmuscle_profiler, test_send_timeout) { wait_time = 60ms; } - MockMMPClient mock_mmp_client(Reference("test_instance"), ""); - Profiler profiler(mock_mmp_client); + Profiler profiler(mock_mmp_client_); ProfileEvent e1( ProfileEventType::receive, ProfileTimestamp(), ProfileTimestamp()); @@ -226,7 +216,7 @@ TEST(libmuscle_profiler, test_send_timeout) { std::this_thread::sleep_for(wait_time); - ASSERT_EQ(mock_mmp_client.last_submitted_profile_events.size(), 1u); - ASSERT_EQ(mock_mmp_client.last_submitted_profile_events.at(0), e1); + ASSERT_EQ(mock_mmp_client_.submit_profile_events.call_arg<0>().size(), 1u); + ASSERT_EQ(mock_mmp_client_.submit_profile_events.call_arg<0>().at(0), e1); } diff --git a/libmuscle/cpp/src/libmuscle/tests/test_snapshot_manager.cpp b/libmuscle/cpp/src/libmuscle/tests/test_snapshot_manager.cpp index 2eaa2b87..594f6f97 100644 --- a/libmuscle/cpp/src/libmuscle/tests/test_snapshot_manager.cpp +++ b/libmuscle/cpp/src/libmuscle/tests/test_snapshot_manager.cpp @@ -4,7 +4,7 @@ #define LIBMUSCLE_MOCK_MMP_CLIENT #define LIBMUSCLE_MOCK_PROFILER -// into the real implementation, +// into the real implementation to test. #include #include @@ -18,12 +18,6 @@ #include #include -// then add mock implementations as needed. -#include -#include -#include -#include - // Test code dependencies #include #include @@ -34,23 +28,16 @@ #include #include #include +#include +#include +#include +#include +#include -// Note: using POSIX for filesystem calls -// Could be upgraded to std::filesystem when targeting C++17 or later -#include -#include -#include -#include -#include -#include -#include using libmuscle::_MUSCLE_IMPL_NS::Data; using libmuscle::_MUSCLE_IMPL_NS::Message; -using libmuscle::_MUSCLE_IMPL_NS::MockCommunicator; -using libmuscle::_MUSCLE_IMPL_NS::MockLogger; -using libmuscle::_MUSCLE_IMPL_NS::MockMMPClient; -using libmuscle::_MUSCLE_IMPL_NS::MockProfiler; +using PortMessageCounts = libmuscle::_MUSCLE_IMPL_NS::MockCommunicator::PortMessageCounts; using libmuscle::_MUSCLE_IMPL_NS::Optional; using libmuscle::_MUSCLE_IMPL_NS::Snapshot; using libmuscle::_MUSCLE_IMPL_NS::SnapshotMetadata; @@ -62,67 +49,24 @@ int main(int argc, char *argv[]) { return RUN_ALL_TESTS(); } -/* Mocks have internal state, which needs to be reset before each test. This - * means that the tests are not reentrant, and cannot be run in parallel. - * It's all fast enough, so that's not a problem. - */ -void reset_mocks() { - MockCommunicator::reset(); - MockMMPClient::reset(); -} - -MockLogger & mock_logger() { - static MockLogger logger; - return logger; -} - -MockProfiler & mock_profiler() { - static MockProfiler profiler; - return profiler; -} - -// callback for nftw() to delete all contents of a folder -int _nftw_rm_callback( - const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf) { - if (tflag == FTW_DP) { - std::cerr << "DEBUG: removing dir " << fpath << std::endl; - return rmdir(fpath); - } - if (tflag == FTW_F) { - std::cerr << "DEBUG: removing file " << fpath << std::endl; - return unlink(fpath); - } - std::cerr << "DEBUG: unknown file type " << fpath << std::endl; - return -1; -} - -class libmuscle_snapshot_manager : public ::testing::Test { - protected: - void SetUp() override { - char tmpname[] = "/tmp/muscle3_test.XXXXXX"; - if (mkdtemp(tmpname) == nullptr) - throw std::runtime_error(strerror(errno)); - temp_dir_ = tmpname; - std::cerr << "DEBUG: using temp dir " << temp_dir_ << std::endl; - - reset_mocks(); - } - - void TearDown() override { - // simulate rm -rf `temp_dir_` using a file-tree-walk - if (nftw(temp_dir_.c_str(), _nftw_rm_callback, 3, FTW_DEPTH) < 0) { - throw std::runtime_error(strerror(errno)); - } - temp_dir_.clear(); - } - - std::string temp_dir_; +struct libmuscle_snapshot_manager : ::testing::Test, TempDirFixture { + RESET_MOCKS( + ::libmuscle::_MUSCLE_IMPL_NS::MockCommunicator, + ::libmuscle::_MUSCLE_IMPL_NS::MockMMPClient, + ::libmuscle::_MUSCLE_IMPL_NS::MockLogger, + ::libmuscle::_MUSCLE_IMPL_NS::MockProfiler); + + // std::string temp_dir_; from TempDirFixture + ::libmuscle::_MUSCLE_IMPL_NS::MockCommunicator mock_communicator_; + ::libmuscle::_MUSCLE_IMPL_NS::MockLogger mock_logger_; + ::libmuscle::_MUSCLE_IMPL_NS::MockProfiler mock_profiler_; + ::libmuscle::_MUSCLE_IMPL_NS::MockMMPClient mock_mmp_client_; }; + TEST_F(libmuscle_snapshot_manager, test_no_checkpointing) { - MockCommunicator communicator("test", {}, {}, mock_logger(), mock_profiler()); - MockMMPClient manager("instance", ""); - SnapshotManager snapshot_manager("test", manager, communicator, mock_logger()); + SnapshotManager snapshot_manager( + "test", mock_mmp_client_, mock_communicator_, mock_logger_); snapshot_manager.prepare_resume({}, temp_dir_); ASSERT_FALSE(snapshot_manager.resuming_from_intermediate()); @@ -130,16 +74,17 @@ TEST_F(libmuscle_snapshot_manager, test_no_checkpointing) { } TEST_F(libmuscle_snapshot_manager, test_save_load_snapshot) { - MockCommunicator communicator("test", {}, {}, mock_logger(), mock_profiler()); - MockCommunicator::PortMessageCounts port_message_counts = { + PortMessageCounts port_message_counts = { {"in", {1}}, {"out", {2}}, {"muscle_settings_in", {0}}}; - MockCommunicator::get_message_counts_return_value = port_message_counts; - MockMMPClient manager("instance", ""); + mock_communicator_.get_message_counts.return_value = port_message_counts; + mock_communicator_.settings_in_connected.return_value = false; + Reference instance_id("test[1]"); - SnapshotManager snapshot_manager(instance_id, manager, communicator, mock_logger()); + SnapshotManager snapshot_manager( + instance_id, mock_mmp_client_, mock_communicator_, mock_logger_); snapshot_manager.prepare_resume({}, temp_dir_); ASSERT_FALSE(snapshot_manager.resuming_from_intermediate()); @@ -148,8 +93,8 @@ TEST_F(libmuscle_snapshot_manager, test_save_load_snapshot) { snapshot_manager.save_snapshot( Message(0.2, "test data"), false, {"test"}, 13.0, {}, {}); - ASSERT_TRUE(MockMMPClient::last_submitted_snapshot_metadata.is_set()); - auto & metadata = MockMMPClient::last_submitted_snapshot_metadata.get(); + ASSERT_TRUE(mock_mmp_client_.submit_snapshot_metadata.called()); + auto const & metadata = mock_mmp_client_.submit_snapshot_metadata.call_arg<0>(); ASSERT_EQ(metadata.triggers, std::vector({"test"})); ASSERT_EQ(metadata.wallclock_time, 13.0); ASSERT_EQ(metadata.timestamp, 0.2); @@ -160,9 +105,12 @@ TEST_F(libmuscle_snapshot_manager, test_save_load_snapshot) { ASSERT_EQ(snapshot_path, temp_dir_ + "/test-1_1.pack"); SnapshotManager snapshot_manager2( - instance_id, manager, communicator, mock_logger()); + instance_id, mock_mmp_client_, mock_communicator_, mock_logger_); snapshot_manager2.prepare_resume(snapshot_path, temp_dir_); - ASSERT_EQ(MockCommunicator::last_restored_message_counts, port_message_counts); + ASSERT_TRUE(mock_communicator_.restore_message_counts.called_once()); + ASSERT_EQ( + mock_communicator_.restore_message_counts.call_arg<0>(), + port_message_counts); ASSERT_TRUE(snapshot_manager2.resuming_from_intermediate()); ASSERT_FALSE(snapshot_manager2.resuming_from_final()); @@ -175,29 +123,29 @@ TEST_F(libmuscle_snapshot_manager, test_save_load_snapshot) { snapshot_manager2.save_snapshot( Message(0.6, "test data2"), true, {"test"}, 42.2, 1.2, {}); - ASSERT_TRUE(MockMMPClient::last_submitted_snapshot_metadata.is_set()); - metadata = MockMMPClient::last_submitted_snapshot_metadata.get(); - ASSERT_EQ(metadata.triggers, std::vector({"test"})); - ASSERT_EQ(metadata.wallclock_time, 42.2); - ASSERT_EQ(metadata.timestamp, 0.6); - ASSERT_FALSE(metadata.next_timestamp.is_set()); - ASSERT_EQ(metadata.port_message_counts, port_message_counts); - ASSERT_TRUE(metadata.is_final_snapshot); - snapshot_path = metadata.snapshot_filename; + ASSERT_TRUE(mock_mmp_client_.submit_snapshot_metadata.called()); + auto const & metadata2 = mock_mmp_client_.submit_snapshot_metadata.call_arg<0>(); + ASSERT_EQ(metadata2.triggers, std::vector({"test"})); + ASSERT_EQ(metadata2.wallclock_time, 42.2); + ASSERT_EQ(metadata2.timestamp, 0.6); + ASSERT_FALSE(metadata2.next_timestamp.is_set()); + ASSERT_EQ(metadata2.port_message_counts, port_message_counts); + ASSERT_TRUE(metadata2.is_final_snapshot); + snapshot_path = metadata2.snapshot_filename; ASSERT_EQ(snapshot_path, temp_dir_ + "/test-1_3.pack"); } TEST_F(libmuscle_snapshot_manager, test_save_load_implicit_snapshot) { - MockCommunicator communicator("test", {}, {}, mock_logger(), mock_profiler()); - MockCommunicator::PortMessageCounts port_message_counts = { + PortMessageCounts port_message_counts = { {"in", {1}}, {"out", {2}}, {"muscle_settings_in", {0}}}; - MockCommunicator::get_message_counts_return_value = port_message_counts; - MockMMPClient manager("instance", ""); + mock_communicator_.get_message_counts.return_value = port_message_counts; + mock_communicator_.settings_in_connected.return_value = false; Reference instance_id("test[1]"); - SnapshotManager snapshot_manager(instance_id, manager, communicator, mock_logger()); + SnapshotManager snapshot_manager( + instance_id, mock_mmp_client_, mock_communicator_, mock_logger_); snapshot_manager.prepare_resume({}, temp_dir_); ASSERT_FALSE(snapshot_manager.resuming_from_intermediate()); @@ -206,21 +154,25 @@ TEST_F(libmuscle_snapshot_manager, test_save_load_implicit_snapshot) { // save implicit snapshot, i.e. Message=not set snapshot_manager.save_snapshot({}, true, {"implicit"}, 1.0, 1.5, {}); - ASSERT_TRUE(MockMMPClient::last_submitted_snapshot_metadata.is_set()); - auto & metadata = MockMMPClient::last_submitted_snapshot_metadata.get(); + ASSERT_TRUE(mock_mmp_client_.submit_snapshot_metadata.called()); + auto const & metadata = mock_mmp_client_.submit_snapshot_metadata.call_arg<0>(); std::string snapshot_path = metadata.snapshot_filename; - MockMMPClient::reset(); + mock_mmp_client_.submit_snapshot_metadata.call_args_list.clear(); SnapshotManager snapshot_manager2( - instance_id, manager, communicator, mock_logger()); + instance_id, mock_mmp_client_, mock_communicator_, mock_logger_); snapshot_manager2.prepare_resume(snapshot_path, temp_dir_); - ASSERT_EQ(MockCommunicator::last_restored_message_counts, port_message_counts); - ASSERT_TRUE(MockMMPClient::last_submitted_snapshot_metadata.is_set()); - MockMMPClient::reset(); + ASSERT_TRUE(mock_communicator_.restore_message_counts.called_once()); + ASSERT_EQ( + mock_communicator_.restore_message_counts.call_arg<0>(), + port_message_counts); + ASSERT_TRUE(mock_mmp_client_.submit_snapshot_metadata.called_once()); + mock_mmp_client_.submit_snapshot_metadata.call_args_list.clear(); ASSERT_FALSE(snapshot_manager2.resuming_from_intermediate()); ASSERT_FALSE(snapshot_manager2.resuming_from_final()); snapshot_manager2.save_snapshot({}, true, {"implicit"}, 12.3, 2.5, {}); - ASSERT_TRUE(MockMMPClient::last_submitted_snapshot_metadata.is_set()); + ASSERT_TRUE(mock_mmp_client_.submit_snapshot_metadata.called_once()); } +