diff --git a/CMakeLists.txt b/CMakeLists.txt index a52e939..08fbec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,6 @@ else() endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) -set(BOOST_ROOT /usr/local) find_package(Boost REQUIRED COMPONENTS system json) include(FetchContent) @@ -25,7 +24,9 @@ function(create_boost_header_only_target target_name) set(interface_target ${target_key}_interface) set(header_subdir ${target_key}) - if(NOT TARGET Boost::${target_name}) + set(target_name_full Boost::${target_name}) + + if(NOT TARGET ${target_name_full}) message(STATUS "Creating Boost::${target_name} as header-only interface target") add_library(${interface_target} INTERFACE) @@ -43,9 +44,9 @@ function(create_boost_header_only_target target_name) endif() # Create alias for consistency with imported targets - add_library(Boost::${target_name} ALIAS ${interface_target}) + add_library(${target_name_full} ALIAS ${interface_target}) else() - message(STATUS "Boost::${target_name} already exists") + message(STATUS "${target_name_full} already exists") endif() endfunction() @@ -83,7 +84,7 @@ add_subdirectory( FetchContent_Declare( asyncmqtt5 - GIT_REPOSITORY https://github.com/mireo/async-mqtt5 + GIT_REPOSITORY https://github.com/boostorg/mqtt5 GIT_TAG master GIT_SHALLOW TRUE ) diff --git a/docker/Dockerfile.baseimage b/docker/Dockerfile.baseimage new file mode 100644 index 0000000..fb41b83 --- /dev/null +++ b/docker/Dockerfile.baseimage @@ -0,0 +1,15 @@ +FROM ubuntu:24.04 + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libboost-json1.83.0 \ + libboost-system1.83.0 \ + liburing2 \ + python3 python3-pip \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install Python test deps globally +#RUN pip3 install pytest paho-mqtt +RUN pip3 install --break-system-packages pytest paho-mqtt + +WORKDIR /app diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 09d8690..398bd73 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -13,7 +13,6 @@ target_include_directories(ranczo-io_utils target_link_libraries(ranczo-io_utils PUBLIC Boost::mqtt5 - Boost::system Boost::json spdlog::spdlog ) diff --git a/libs/mqtt_client.cpp b/libs/mqtt_client.cpp index 1c3c27d..d41e02e 100644 --- a/libs/mqtt_client.cpp +++ b/libs/mqtt_client.cpp @@ -220,7 +220,7 @@ struct AsyncMqttClient::AsyncMqttClientImpl { AsyncMqttClient::AsyncMqttClient(const boost::asio::any_io_executor & executor) : _impl{std::make_unique< AsyncMqttClient::AsyncMqttClientImpl >(executor)} {} -awaitable_expected AsyncMqttClient::subscribe(std::string_view topic, std::function< awaitable_expected< void >(const boost::json::value &) > cb) noexcept { +awaitable_expected AsyncMqttClient::subscribe(std::string_view topic, callback_t&& cb) noexcept { BOOST_ASSERT(_impl); BOOST_ASSERT(not topic.empty()); BOOST_ASSERT(cb); diff --git a/libs/ranczo-io/utils/mqtt_client.hpp b/libs/ranczo-io/utils/mqtt_client.hpp index c48248c..84b3ce5 100644 --- a/libs/ranczo-io/utils/mqtt_client.hpp +++ b/libs/ranczo-io/utils/mqtt_client.hpp @@ -30,6 +30,8 @@ class AsyncMqttClient { /* value assosiated to request */ const boost::json::value & value; }; + + using callback_t = std::function< awaitable_expected< void >(const boost::json::value & value) >; struct AsyncMqttClientImpl; std::unique_ptr< AsyncMqttClientImpl > _impl; @@ -38,8 +40,7 @@ class AsyncMqttClient { ~AsyncMqttClient(); /* subscribes to a topic, topic can contain wildcards */ - awaitable_expected< void > subscribe(std::string_view topic, - std::function< awaitable_expected< void >(const boost::json::value & value) >) noexcept; + awaitable_expected< void > subscribe(std::string_view topic, callback_t &&cb) noexcept; awaitable_expected< const boost::json::value & > request(std::string_view topic, const boost::json::value & value) noexcept; awaitable_expected< void > publish(std::string_view topic, const boost::json::value & value) noexcept; diff --git a/services/floorheat_svc/CMakeLists.txt b/services/floorheat_svc/CMakeLists.txt index 82d4b70..5200ce2 100644 --- a/services/floorheat_svc/CMakeLists.txt +++ b/services/floorheat_svc/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(ranczo-io_floorheating main.cpp - heater._controller.cpp + heater_controller.cpp relay.cpp ) @@ -17,3 +17,15 @@ install( LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + +enable_testing() + +add_test(NAME ranczo_io_floorheating_integration + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/integration_tests/run_docker_integration_tests.sh +) + +set_tests_properties(ranczo_io_floorheating_integration PROPERTIES + ENVIRONMENT INSTALL_DIR=${CMAKE_BINARY_DIR}/install + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/integration_tests + TIMEOUT 120 +) diff --git a/services/floorheat_svc/heater._controller.cpp b/services/floorheat_svc/heater_controller.cpp similarity index 100% rename from services/floorheat_svc/heater._controller.cpp rename to services/floorheat_svc/heater_controller.cpp diff --git a/services/floorheat_svc/integration_tests/Dockerfile b/services/floorheat_svc/integration_tests/Dockerfile new file mode 100644 index 0000000..5e4b44a --- /dev/null +++ b/services/floorheat_svc/integration_tests/Dockerfile @@ -0,0 +1,6 @@ +FROM ranczo-io_baseimage:latest + +# Copy tests into /tests +COPY . /tests/ +WORKDIR /tests +CMD ["pytest", "-v"] diff --git a/services/floorheat_svc/integration_tests/docker-compose.yml b/services/floorheat_svc/integration_tests/docker-compose.yml new file mode 100644 index 0000000..1858eb7 --- /dev/null +++ b/services/floorheat_svc/integration_tests/docker-compose.yml @@ -0,0 +1,28 @@ +version: "3.9" + +services: + mqtt: + image: eclipse-mosquitto + ports: + - "1883:1883" + + floorheating_svc: + image: ranczo-io_baseimage:latest + volumes: + - ${INSTALL_DIR}/:/app + # - ${INSTALL_DIR}/etc/ranczo-io_config:/app/ig:ro # optional + working_dir: /app + command: ["./bin/ranczo-io_floorheating"] + depends_on: + - mqtt + + tests: + build: + context: . + dockerfile: Dockerfile + # volumes: + # - ../../../build/install/:/install:ro # expose binaries if needed + depends_on: + - mqtt + - floorheating_svc + diff --git a/services/floorheat_svc/integration_tests/run_docker_integration_tests.sh b/services/floorheat_svc/integration_tests/run_docker_integration_tests.sh new file mode 100755 index 0000000..e54b2d2 --- /dev/null +++ b/services/floorheat_svc/integration_tests/run_docker_integration_tests.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +# Allow overriding from env +INSTALL_DIR="${INSTALL_DIR:-${CMAKE_BINARY_DIR}/install}" +BUILD_DIR="$(dirname "$INSTALL_DIR")" + +echo "[INFO] Using INSTALL_DIR=$INSTALL_DIR" +echo "[INFO] Inferred BUILD_DIR=$BUILD_DIR" + +# 1. Build the project first +echo "[INFO] Building project..." +cmake --build "$BUILD_DIR" + +# 2. Then install to INSTALL_DIR +echo "[INFO] Installing to $INSTALL_DIR..." +cmake --install "$BUILD_DIR" --prefix "$INSTALL_DIR" + +# 3. Run docker-compose tests +echo "[INFO] Running docker compose integration tests..." +docker compose -f docker-compose.yml up --build --abort-on-container-exit diff --git a/services/floorheat_svc/integration_tests/test_ok.py b/services/floorheat_svc/integration_tests/test_ok.py new file mode 100644 index 0000000..3b67649 --- /dev/null +++ b/services/floorheat_svc/integration_tests/test_ok.py @@ -0,0 +1,6 @@ +import pytest + + +def test_assert(): + assert True + diff --git a/services/floorheat_svc/main.cpp b/services/floorheat_svc/main.cpp index fdf12be..5b0944e 100644 --- a/services/floorheat_svc/main.cpp +++ b/services/floorheat_svc/main.cpp @@ -14,6 +14,7 @@ #include #include +#include namespace ranczo { @@ -30,12 +31,28 @@ namespace ranczo { using namespace std::chrono_literals; using namespace std::string_view_literals; +std::atomic running = true; +boost::asio::io_context* g_io = nullptr; + +void signal_handler(int signum) { + spdlog::warn("Signal received: {}, stopping io_context...", signum); + running = false; + if (g_io) { + g_io->stop(); // <--- This stops io_context.run() + } +} + int main() { spdlog::set_level(spdlog::level::trace); - std::vector< std::shared_ptr< ranczo::IHeater > > _heaters; boost::asio::io_context io_context; + g_io = &io_context; + + // Register signal handler + spdlog::info("Registering signal handlers"); + std::signal(SIGINT, signal_handler); + std::signal(SIGTERM, signal_handler); boost::asio::any_io_executor io_executor = io_context.get_executor(); // PARTER