diff --git a/etc/docker/tests/run_test.sh b/etc/docker/tests/run_test.sh index bd1a75ce3..e5d45b7e5 100755 --- a/etc/docker/tests/run_test.sh +++ b/etc/docker/tests/run_test.sh @@ -7,5 +7,9 @@ cmake .. -G Ninja -DVIAMCPPSDK_USE_DYNAMIC_PROTOS=ON \ -DVIAMCPPSDK_CLANG_TIDY=ON ninja all ninja install -cd src/viam/sdk/tests +pushd src/viam/sdk/tests UBSAN_OPTIONS="print_stacktrace=1" ctest --output-on-failure +popd +pushd src/viam/examples/modules/complex +UBSAN_OPTIONS="print_stacktrace=1" ctest --output-on-failure +popd diff --git a/src/viam/examples/modules/complex/CMakeLists.txt b/src/viam/examples/modules/complex/CMakeLists.txt index 0d5e1d8c0..a3d4fb4c6 100644 --- a/src/viam/examples/modules/complex/CMakeLists.txt +++ b/src/viam/examples/modules/complex/CMakeLists.txt @@ -148,3 +148,27 @@ install( TARGETS complex_module_client COMPONENT examples ) + +# Testing related cmake logic; the section below is likely irrelevant for users +# writing their own modules. We are simply integrating tests from +# `test_complex_module.cpp` into the Viam C++ SDK testing suite. + +enable_testing() +target_sources(viamsdk_test + PRIVATE + gizmo/impl.cpp + gizmo/impl.hpp + gizmo/api.cpp + gizmo/api.hpp + summation/api.cpp + summation/api.hpp + summation/impl.cpp + summation/impl.hpp + + ${MODULE_PROTO_OUTPUT_FILES} +) +target_include_directories(viamsdk_test + PUBLIC + ${MODULE_PROTO_GEN_DIR} +) +viamcppsdk_add_boost_test(test_complex_module.cpp) diff --git a/src/viam/examples/modules/complex/README.md b/src/viam/examples/modules/complex/README.md index 77cf36dbe..af80f69f4 100644 --- a/src/viam/examples/modules/complex/README.md +++ b/src/viam/examples/modules/complex/README.md @@ -27,6 +27,8 @@ In the main directory, there is also a `main.cpp` file, which creates a module, Finally, the `client.cpp` file can be used to test the module once you have connected to your robot and configured it. You will have to update the credentials and robot address in that file before building. After building, the `build/install/bin/complex_module_client` generated binary can be called to run the client. +`test_complex_module.cpp` is used by the Viam team to ensure the complex module example works as expected. + ## Configuring and using the module The `complex_module` binary generated after building is the entrypoint for this module. To connect this module with your robot, you must add this module's entrypoint to the robot's config. For example, the entrypoint file may be at `/home/viam-cpp-sdk/build/install/bin/complex_module` and you must add this file path to your configuration. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#use-a-modular-resource-with-your-robot) for more details. diff --git a/src/viam/examples/modules/complex/client.cpp b/src/viam/examples/modules/complex/client.cpp index 2a33ee291..8bed5b360 100644 --- a/src/viam/examples/modules/complex/client.cpp +++ b/src/viam/examples/modules/complex/client.cpp @@ -56,7 +56,7 @@ int main() { } bool do_one_ret = gc->do_one("arg1"); std::cout << "gizmo1 do_one returned: " << do_one_ret; - bool do_one_client_stream_ret = gc->do_one_client_stream({"arg1", "arg2", "arg3"}); + bool do_one_client_stream_ret = gc->do_one_client_stream({"arg1", "arg1", "arg1"}); std::cout << "gizmo1 do_one_client_stream returned: " << do_one_client_stream_ret; std::string do_two_ret = gc->do_two(false); std::cout << "gizmo1 do_two returned: " << do_two_ret; diff --git a/src/viam/examples/modules/complex/gizmo/api.cpp b/src/viam/examples/modules/complex/gizmo/api.cpp index 3a3f74e25..f96ce87b1 100644 --- a/src/viam/examples/modules/complex/gizmo/api.cpp +++ b/src/viam/examples/modules/complex/gizmo/api.cpp @@ -225,6 +225,7 @@ bool GizmoClient::do_one_client_stream(std::vector arg1) { auto writer(stub_->DoOneClientStream(&ctx, &response)); for (std::string arg : arg1) { DoOneClientStreamRequest curr_req = {}; + *curr_req.mutable_name() = this->name(); curr_req.set_arg1(arg); if (!writer->Write(curr_req)) { // Stream is broken; stop writing. @@ -244,6 +245,9 @@ std::vector GizmoClient::do_one_server_stream(std::string arg1) { DoOneServerStreamRequest request; grpc::ClientContext ctx; + *request.mutable_name() = this->name(); + request.set_arg1(arg1); + auto reader(stub_->DoOneServerStream(&ctx, request)); DoOneServerStreamResponse curr_resp = {}; std::vector rets = {}; @@ -264,6 +268,7 @@ std::vector GizmoClient::do_one_bidi_stream(std::vector arg1) auto stream(stub_->DoOneBiDiStream(&ctx)); for (std::string arg : arg1) { DoOneBiDiStreamRequest curr_req = {}; + *curr_req.mutable_name() = this->name(); curr_req.set_arg1(arg); if (!stream->Write(curr_req)) { // Stream is broken; stop writing. diff --git a/src/viam/examples/modules/complex/gizmo/impl.hpp b/src/viam/examples/modules/complex/gizmo/impl.hpp index 582fe75ca..fe6dcaf6e 100644 --- a/src/viam/examples/modules/complex/gizmo/impl.hpp +++ b/src/viam/examples/modules/complex/gizmo/impl.hpp @@ -11,6 +11,7 @@ using namespace viam::sdk; // `validate` method that checks config validity. class MyGizmo : public Gizmo { public: + MyGizmo(std::string name, std::string arg1) : Gizmo(std::move(name)), arg1_(std::move(arg1)){}; MyGizmo(Dependencies deps, ResourceConfig cfg) : Gizmo(cfg.name()) { this->reconfigure(deps, cfg); }; diff --git a/src/viam/examples/modules/complex/summation/impl.hpp b/src/viam/examples/modules/complex/summation/impl.hpp index 8e74f1ac9..cdef63153 100644 --- a/src/viam/examples/modules/complex/summation/impl.hpp +++ b/src/viam/examples/modules/complex/summation/impl.hpp @@ -10,6 +10,8 @@ using namespace viam::sdk; // implements all relevant methods along with `reconfigure`. class MySummation : public Summation { public: + MySummation(std::string name, bool subtract) + : Summation(std::move(name)), subtract_(subtract){}; MySummation(Dependencies deps, ResourceConfig cfg) : Summation(cfg.name()) { this->reconfigure(deps, cfg); }; diff --git a/src/viam/examples/modules/complex/test_complex_module.cpp b/src/viam/examples/modules/complex/test_complex_module.cpp new file mode 100644 index 000000000..08cbb85d6 --- /dev/null +++ b/src/viam/examples/modules/complex/test_complex_module.cpp @@ -0,0 +1,216 @@ +#define BOOST_TEST_MODULE test module test_complex_module + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "gizmo.grpc.pb.h" +#include "gizmo.pb.h" +#include "gizmo/api.hpp" +#include "gizmo/impl.hpp" +#include "summation.grpc.pb.h" +#include "summation.pb.h" +#include "summation/api.hpp" +#include "summation/impl.hpp" + +using namespace viam::sdk; + +BOOST_AUTO_TEST_SUITE(test_gizmo_impl) + +// get_gizmo creates a MyGizmo for testing purposes named "testgizmo" with +// `arg1_` set to "foo". +std::shared_ptr get_gizmo() { + return std::make_shared("testgizmo", "foo"); +} + +BOOST_AUTO_TEST_CASE(impl_do_one) { + auto gizmo = get_gizmo(); + BOOST_CHECK(gizmo->do_one("foo")); + BOOST_CHECK(!gizmo->do_one("bar")); +} + +BOOST_AUTO_TEST_CASE(impl_do_one_client_stream) { + auto gizmo = get_gizmo(); + BOOST_CHECK(gizmo->do_one_client_stream({"foo", "foo"})); + BOOST_CHECK(!gizmo->do_one_client_stream({"foo", "bar"})); +} + +BOOST_AUTO_TEST_CASE(impl_do_one_server_stream) { + auto gizmo = get_gizmo(); + std::vector ret1 = {true, false, true, false}; + std::vector ret2 = {false, false, true, false}; + BOOST_CHECK(gizmo->do_one_server_stream("foo") == ret1); + BOOST_CHECK(gizmo->do_one_server_stream("bar") == ret2); +} + +BOOST_AUTO_TEST_CASE(impl_do_one_bidi_stream) { + auto gizmo = get_gizmo(); + std::vector ret1 = {true, false}; + std::vector ret2 = {false, true}; + BOOST_CHECK(gizmo->do_one_bidi_stream({"foo", "bar"}) == ret1); + BOOST_CHECK(gizmo->do_one_bidi_stream({"bar", "foo"}) == ret2); +} + +BOOST_AUTO_TEST_CASE(impl_do_two) { + auto gizmo = get_gizmo(); + BOOST_CHECK(gizmo->do_two(true) == "arg1=true"); + BOOST_CHECK(gizmo->do_two(false) == "arg1=false"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(test_gizmo_client_server) + +// This sets up the following architecture +// -- MockComponent +// /\ +// +// | (function calls) +// +// \/ +// -- ComponentServer (Real) +// /\ +// +// | (grpc InProcessChannel) +// +// \/ +// -- ComponentClient (Real) +// +// This is as close to a real setup as we can get +// without starting another process +// +// The passed in lambda function has access to the ComponentClient +template +void gizmo_server_to_mock_pipeline(Lambda&& func) { + GizmoServer gizmo_server; + gizmo_server.resource_manager()->add(std::string("testgizmo"), test_gizmo_impl::get_gizmo()); + + grpc::ServerBuilder builder; + builder.RegisterService(&gizmo_server); + + std::unique_ptr server = builder.BuildAndStart(); + + grpc::ChannelArguments args; + auto grpc_channel = server->InProcessChannel(args); + GizmoClient client("testgizmo", grpc_channel); + // Run the passed test on the created stack + std::forward(func)(client); + // shutdown afterwards + server->Shutdown(); +} + +BOOST_AUTO_TEST_CASE(test_do_one) { + gizmo_server_to_mock_pipeline([](Gizmo& client) -> void { + BOOST_CHECK(client.do_one("foo")); + BOOST_CHECK(!client.do_one("bar")); + }); +} + +BOOST_AUTO_TEST_CASE(test_do_one_client_stream) { + gizmo_server_to_mock_pipeline([](Gizmo& client) -> void { + BOOST_CHECK(client.do_one_client_stream({"foo", "foo"})); + BOOST_CHECK(!client.do_one_client_stream({"foo", "bar"})); + }); +} + +BOOST_AUTO_TEST_CASE(test_do_one_server_stream) { + gizmo_server_to_mock_pipeline([](Gizmo& client) -> void { + std::vector ret1 = {true, false, true, false}; + std::vector ret2 = {false, false, true, false}; + BOOST_CHECK(client.do_one_server_stream("foo") == ret1); + BOOST_CHECK(client.do_one_server_stream("bar") == ret2); + }); +} + +BOOST_AUTO_TEST_CASE(test_do_one_bidi_stream) { + gizmo_server_to_mock_pipeline([](Gizmo& client) -> void { + std::vector ret1 = {true, false}; + std::vector ret2 = {false, true}; + BOOST_CHECK(client.do_one_bidi_stream({"foo", "bar"}) == ret1); + BOOST_CHECK(client.do_one_bidi_stream({"bar", "foo"}) == ret2); + }); +} + +BOOST_AUTO_TEST_CASE(test_do_two) { + gizmo_server_to_mock_pipeline([](Gizmo& client) -> void { + BOOST_CHECK(client.do_two(true) == "arg1=true"); + BOOST_CHECK(client.do_two(false) == "arg1=false"); + }); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(test_summation_impl) + +// get_summation creates a MySummation for testing purposes named "testsum" +// with `subtract_` set to false. +std::shared_ptr get_summation() { + return std::make_shared("testsum", false); +} + +BOOST_AUTO_TEST_CASE(impl_sum) { + auto summation = get_summation(); + double sum = summation->sum({0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}); + BOOST_CHECK(sum == 45); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(test_summation_client_server) + +// This sets up the following architecture +// -- MockComponent +// /\ +// +// | (function calls) +// +// \/ +// -- ComponentServer (Real) +// /\ +// +// | (grpc InProcessChannel) +// +// \/ +// -- ComponentClient (Real) +// +// This is as close to a real setup as we can get +// without starting another process +// +// The passed in lambda function has access to the ComponentClient +// +template +void sum_server_to_mock_pipeline(Lambda&& func) { + SummationServer sum_server; + sum_server.resource_manager()->add(std::string("testsum"), + test_summation_impl::get_summation()); + + grpc::ServerBuilder builder; + builder.RegisterService(&sum_server); + + std::unique_ptr server = builder.BuildAndStart(); + + grpc::ChannelArguments args; + auto grpc_channel = server->InProcessChannel(args); + SummationClient client("testsum", grpc_channel); + // Run the passed test on the created stack + std::forward(func)(client); + // shutdown afterwards + server->Shutdown(); +} + +BOOST_AUTO_TEST_CASE(test_sum) { + sum_server_to_mock_pipeline([](Summation& client) -> void { + double sum = client.sum({0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}); + BOOST_CHECK(sum == 45); + }); +} + +BOOST_AUTO_TEST_SUITE_END()