diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 735fe30..80dea63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,121 +9,11 @@ name: CI on: [push, pull_request] -env: - BOOST_VERSION: 1.82.0 - BOOST_DIR_VER_NAME: 1_82_0 - jobs: - windows: - name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }}" - defaults: - run: - shell: cmd - - strategy: - fail-fast: false - matrix: - include: - # internal compiler error - # - toolset: msvc-14.2 win64 - # os: windows-2019 - # architecture: x64 - # generator: "Visual Studio 16 2019" - # cxxstd: 17 - # build-type: 'Debug' - # cxxflags: '' - # ldflags: '/machine:x64' - - - toolset: msvc-14.3 win32 - os: windows-2022 - architecture: Win32 - generator: Visual Studio 17 2022 - cxxstd: 20 - build-type: 'Debug' - cxxflags: '' - ldflags: '' - - - toolset: msvc-14.3 win64 - os: windows-2022 - architecture: x64 - generator: Visual Studio 17 2022 - cxxstd: 20 - build-type: 'Debug' - cxxflags: '' - ldflags: '' - - runs-on: ${{ matrix.os }} - env: - CXXFLAGS: ${{ matrix.cxxflags }} /D_WIN32_WINNT=0x0601 /DWIN32_LEAN_AND_MEAN=1 /DNOMINMAX=1 /D_FILE_OFFSET_BITS=64 /DBOOST_ALL_NO_LIB /EHsc /bigobj - LDFLAGS: ${{ matrix.ldflags }} - CMAKE_BUILD_PARALLEL_LEVEL: 4 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup OpenSSL - env: - OPENSSL_ROOT: "C:\\OpenSSL" - run: | - if "${{ matrix.architecture }}" == "x64" ( - choco install --no-progress -y openssl --x64 - ) - if "${{ matrix.architecture }}" == "Win32" ( - set openssl_install_dir="C:\\Program Files (x86)\\OpenSSL-Win32" - choco install --no-progress -y openssl --forcex86 --version 1.1.1.2100 - ) - if "${{ matrix.architecture }}" == "x64" ( - if exist "C:\Program Files\OpenSSL\" ( - set openssl_install_dir="C:\\Program Files\\OpenSSL" - ) else ( - set openssl_install_dir="C:\\Program Files\\OpenSSL-Win64" - ) - ) - mklink /D %OPENSSL_ROOT% %openssl_install_dir% - refreshenv - set - - - name: Setup Cmake - run: | - choco install cmake - - - name: Setup Boost - run: | - curl -o boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz https://archives.boost.io/release/${{ env.BOOST_VERSION }}/source/boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz - tar -xf boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz - mkdir ${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }} - move boost_${{ env.BOOST_DIR_VER_NAME }}\\boost ${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }} - del boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz - - - name: Setup library - run: | - cmake -S . -B build -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --install build - - - name: Build examples - run: | - cmake -S example -B example\\build -A ${{ matrix.architecture }} ^ - -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" ^ - -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" ^ - -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build example\\build -j 4 - - - name: Build tests - run: | - cmake -S test -B test\\build -A ${{ matrix.architecture }} ^ - -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" ^ - -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" ^ - -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build test\\build -j 4 - - - name: Run tests - run: | - .\\test\\build\\${{ matrix.build-type }}\\mqtt-test.exe - - posix: - name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.container }}" + # TODO: windows builds + posix-cmake: + name: "CMake ${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.cxxflags }}" defaults: run: shell: bash @@ -132,8 +22,7 @@ jobs: fail-fast: false matrix: include: - - toolset: g++-9 - compiler: g++-9 + - toolset: gcc-9 install: g++-9 os: ubuntu-latest container: ubuntu:22.04 @@ -142,8 +31,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: g++-10 - compiler: g++-10 + - toolset: gcc-10 install: g++-10 os: ubuntu-latest container: ubuntu:22.04 @@ -152,8 +40,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: g++-12 - compiler: g++-12 + - toolset: gcc-12 install: g++-12 os: ubuntu-latest container: ubuntu:22.04 @@ -162,8 +49,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: g++-13 - compiler: g++-13 + - toolset: gcc-13 install: g++-13 os: ubuntu-24.04 container: ubuntu:24.04 @@ -172,8 +58,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: g++-14 -fsanitize=address,undefined - compiler: g++-14 + - toolset: gcc-14 install: g++-14 os: ubuntu-24.04 container: ubuntu:24.04 @@ -182,8 +67,7 @@ jobs: cxxflags: '-fsanitize=address,undefined -fno-sanitize-recover=all' ldflags: '-fsanitize=address,undefined' - - toolset: clang++-12 - compiler: clang++-12 + - toolset: clang-12 install: clang++-12 os: ubuntu-latest container: ubuntu:22.04 @@ -192,8 +76,7 @@ jobs: cxxflags: '-fdeclspec' ldflags: '' - - toolset: clang++-13 - compiler: clang++-13 + - toolset: clang-13 install: clang++-13 os: ubuntu-latest container: ubuntu:22.04 @@ -202,8 +85,7 @@ jobs: cxxflags: '-fdeclspec' ldflags: '' - - toolset: clang++-14-libc++-14 - compiler: clang++-14 + - toolset: clang-14 install: 'clang++-14 libc++-14-dev libc++abi-14-dev' os: ubuntu-latest container: ubuntu:22.04 @@ -212,8 +94,7 @@ jobs: cxxflags: '-stdlib=libc++' ldflags: '-lc++' - - toolset: clang++-15 - compiler: clang++-15 + - toolset: clang-15 install: clang++-15 os: ubuntu-latest container: ubuntu:22.04 @@ -222,8 +103,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: clang++-16 - compiler: clang++-16 + - toolset: clang-16 install: clang++-16 os: ubuntu-24.04 container: ubuntu:24.04 @@ -232,8 +112,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: clang++-17 - compiler: clang++-17 + - toolset: clang-17 install: clang++-17 os: ubuntu-24.04 container: ubuntu:24.04 @@ -242,8 +121,7 @@ jobs: cxxflags: '' ldflags: '' - - toolset: clang++-18 -fsanitize=address,undefined - compiler: clang++-18 + - toolset: clang-18 install: clang++-18 os: ubuntu-24.04 container: ubuntu:24.04 @@ -267,40 +145,131 @@ jobs: if: matrix.container run: | apt-get update - apt-get -y install sudo wget tar cmake openssl libssl-dev pkg-config + apt-get -y install --no-install-recommends \ + sudo git g++ cmake make openssl libssl-dev ca-certificates pkg-config python3 - - name: Install compiler + - name: Install toolset run: sudo apt-get install -y ${{ matrix.install }} - name: Setup Boost run: | - wget https://archives.boost.io/release/${{ env.BOOST_VERSION }}/source/boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz - tar xzf boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz - mkdir /usr/local/boost_${{ env.BOOST_DIR_VER_NAME }} - mv boost_${{ env.BOOST_DIR_VER_NAME }}/boost /usr/local/boost_${{ env.BOOST_DIR_VER_NAME }} - rm boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz + python3 tools/ci.py setup-boost \ + --source-dir=$(pwd) - - name: Setup library - run: | - cmake -S . -B build -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" \ - -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" - sudo cmake --install build + - name: Build a Boost distribution using B2 + run : | + python3 tools/ci.py build-b2-distro \ + --toolset ${{ matrix.toolset }} - - name: Build examples + - name: Build a Boost distribution using CMake run: | - cmake -S example -B example/build \ - -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" \ - -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ - -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build example/build -j 4 + python3 tools/ci.py build-cmake-distro \ + --build-type ${{ matrix.build-type }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --toolset ${{ matrix.toolset }} + + - name: Build standalone examples using CMake + run: | + python3 tools/ci.py build-cmake-standalone-examples \ + --build-type ${{ matrix.build-type }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --toolset ${{ matrix.toolset }} + + - name: Build standalone tests using CMake + run: | + python3 tools/ci.py build-cmake-standalone-tests \ + --build-type ${{ matrix.build-type }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --toolset ${{ matrix.toolset }} + + - name: Run standalone tests + run: | + python3 tools/ci.py run-cmake-standalone-tests \ + --build-type ${{ matrix.build-type }} + + - name: Run CMake find_package test with B2 distribution + run: | + python3 tools/ci.py run-cmake-b2-find-package-tests \ + --build-type ${{ matrix.build-type }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --toolset ${{ matrix.toolset }} + + - name: Run CMake find_package test with CMake distribution + run : | + python3 tools/ci.py run-cmake-find-package-tests \ + --build-type ${{ matrix.build-type }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --toolset ${{ matrix.toolset }} + + - name: Run CMake add_subdirectory test with CMake distribution + run: | + python3 tools/ci.py run-cmake-add-subdirectory-tests \ + --build-type ${{ matrix.build-type }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --toolset ${{ matrix.toolset }} + + posix-b2: + name: "B2 ${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.cxxflags }}" + + defaults: + run: + shell: bash + + strategy: + fail-fast: false + matrix: + include: + - toolset: gcc-11 + install: g++-11 + os: ubuntu-latest + container: ubuntu:22.04 + build-type: 'debug,release' + cxxstd: "17,20" + cxxflags: '' + ldflags: '' + + - toolset: clang-14 + install: clang-14 + os: ubuntu-latest + container: ubuntu:22.04 + build-type: 'debug,release' + cxxstd: "17,20" + cxxflags: '' + ldflags: '' + + runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} + env: + CXXFLAGS: ${{ matrix.cxxflags }} -Wall -Wextra + LDFLAGS: ${{ matrix.ldflags }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup container environment + if: matrix.container + run: | + apt-get update + apt-get -y install --no-install-recommends \ + sudo git g++ ca-certificates pkg-config python3 + + - name: Install toolset + run: sudo apt-get install -y ${{ matrix.install }} + + - name: Setup Boost + run: | + python3 tools/ci.py setup-boost \ + --source-dir=$(pwd) + + - name: Build a Boost distribution using B2 + run : | + python3 tools/ci.py build-b2-distro \ + --toolset ${{ matrix.toolset }} - - name: Build tests + - name: Build and run tests using B2 run: | - cmake -S test -B test/build \ - -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" \ - -DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \ - -DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}" - cmake --build test/build -j 4 - - - name: Run tests - run: ./test/build/mqtt-test + python3 tools/ci.py run-b2-tests \ + --toolset ${{ matrix.toolset }} \ + --cxxstd ${{ matrix.cxxstd }} \ + --variant ${{ matrix.build-type }} diff --git a/CMakeLists.txt b/CMakeLists.txt index a8f8474..0bb88da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,50 +1,80 @@ -cmake_minimum_required(VERSION 3.15) +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# -project(async-mqtt5 VERSION 1.0.2 LANGUAGES CXX) +cmake_minimum_required(VERSION 3.8...3.20) + +project(boost_mqtt5 VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) include(cmake/project-is-top-level.cmake) -include(cmake/variables.cmake) -add_library(async_mqtt5 INTERFACE) -add_library(Async::MQTT5 ALIAS async_mqtt5) - -set_property( - TARGET async_mqtt5 PROPERTY - EXPORT_NAME MQTT5 -) - -target_include_directories( - async_mqtt5 ${warning_guard} - INTERFACE - "$" -) - -target_compile_features(async_mqtt5 INTERFACE cxx_std_17) - -find_package(Boost 1.82 REQUIRED) -target_link_libraries(async_mqtt5 - INTERFACE - Boost::headers -) - -if(NOT CMAKE_SKIP_INSTALL_RULES) - include(cmake/install-rules.cmake) +# Determine if this is the superproject or called from add_subdirectory. +if(NOT DEFINED BOOST_MQTT5_MAIN_PROJECT) + set(BOOST_MQTT5_MAIN_PROJECT OFF) + if(PROJECT_IS_TOP_LEVEL) + set(BOOST_MQTT5_MAIN_PROJECT ON) + endif() endif() -if(PROJECT_IS_TOP_LEVEL) - option(BUILD_EXAMPLES "Build examples tree." "${async-mqtt5_DEVELOPER_MODE}") +add_library(boost_mqtt5 INTERFACE) +add_library(Boost::mqtt5 ALIAS boost_mqtt5) + +# If non-Boost dependencies are not found, we just bail out. +find_package(Threads) +if(NOT Threads_FOUND) + message(STATUS "Boost.MQTT5 has been disabled, because the required package Threads hasn't been found") + return() +endif() + +target_include_directories(boost_mqtt5 INTERFACE include) +target_compile_features(boost_mqtt5 INTERFACE cxx_std_17) + +if(BOOST_MQTT5_MAIN_PROJECT) + find_package(Boost 1.82 REQUIRED) + if (NOT Boost_FOUND) + message(STATUS "Cannot find Boost!") + return() + endif() + target_link_libraries(boost_mqtt5 INTERFACE Boost::headers Threads::Threads) +else() + target_link_libraries( + boost_mqtt5 + INTERFACE + Boost::asio + Boost::assert + # Boost::beast # Optional, only used for MQTT connections over WebSocket. + Boost::container + Boost::core + Boost::endian + Boost::fusion + Boost::optional + Boost::random + Boost::range + Boost::smart_ptr + Boost::spirit + Boost::system + Boost::type_traits + Threads::Threads + ) +endif() + +option(BOOST_MQTT5_PUBLIC_BROKER_TESTS OFF "Whether to run tests requiring a public MQTT broker") +mark_as_advanced(BOOST_MQTT5_PUBLIC_BROKER_TESTS) + +if(BUILD_TESTING) + # Custom target tests; required by the Boost superproject + if(NOT TARGET tests) + add_custom_target(tests) + endif() + add_subdirectory(test) +endif() + +if(BOOST_MQTT5_MAIN_PROJECT) + option(BUILD_EXAMPLES "Whether to build examples") if(BUILD_EXAMPLES) add_subdirectory(example) endif() endif() - -if(NOT async-mqtt5_DEVELOPER_MODE) - return() -elseif(NOT PROJECT_IS_TOP_LEVEL) - message( - AUTHOR_WARNING - "Developer mode is intended for developers of async-mqtt5" - ) -endif() - -include(cmake/dev-mode.cmake) diff --git a/build.jam b/build.jam new file mode 100644 index 0000000..d44988d --- /dev/null +++ b/build.jam @@ -0,0 +1,37 @@ +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +require-b2 5.2 ; + +constant boost_dependencies : + /boost/asio//boost_asio + /boost/assert//boost_assert + /boost/container//boost_container + /boost/core//boost_core + /boost/endian//boost_endian + /boost/fusion//boost_fusion + /boost/optional//boost_optional + /boost/random//boost_random + /boost/range//boost_range + /boost/smart_ptr//boost_smart_ptr + /boost/spirit//boost_spirit + /boost/system//boost_system + /boost/type_traits//boost_type_traits + ; + +project /boost/mqtt5 + : common-requirements + include + ; + +explicit + [ alias boost_mqtt5 : : : : $(boost_dependencies) ] + [ alias all : boost_mqtt5 test ] + ; + +call-if : boost-library mqtt5 + ; diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake deleted file mode 100644 index 92cbfc5..0000000 --- a/cmake/dev-mode.cmake +++ /dev/null @@ -1,4 +0,0 @@ -include(CTest) -if(BUILD_TESTING) - add_subdirectory(test) -endif() diff --git a/cmake/install-config.cmake b/cmake/install-config.cmake deleted file mode 100644 index cb60343..0000000 --- a/cmake/install-config.cmake +++ /dev/null @@ -1,4 +0,0 @@ -include(CMakeFindDependencyMacro) -find_dependency(Boost 1.82) - -include("${CMAKE_CURRENT_LIST_DIR}/async-mqtt5Targets.cmake") diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake deleted file mode 100644 index b698e26..0000000 --- a/cmake/install-rules.cmake +++ /dev/null @@ -1,66 +0,0 @@ -if(PROJECT_IS_TOP_LEVEL) - set( - CMAKE_INSTALL_INCLUDEDIR "/async-mqtt5-${PROJECT_VERSION}" - CACHE STRING "" - ) - set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH) -endif() - -# Project is configured with no languages, so tell GNUInstallDirs the lib dir -set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "") - -include(CMakePackageConfigHelpers) -include(GNUInstallDirs) - -# find_package() call for consumers to find this project -set(package async-mqtt5) - -install( - DIRECTORY include/ - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - COMPONENT async-mqtt5_Development -) - -install( - TARGETS async_mqtt5 - EXPORT async-mqtt5Targets - INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" -) - -write_basic_package_version_file( - "${package}ConfigVersion.cmake" - COMPATIBILITY SameMajorVersion - ARCH_INDEPENDENT -) - -# Allow package maintainers to freely override the path for the configs -set( - async-mqtt5_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}" - CACHE STRING "CMake package config location relative to the install prefix" -) -set_property(CACHE async-mqtt5_INSTALL_CMAKEDIR PROPERTY TYPE PATH) -mark_as_advanced(async-mqtt5_INSTALL_CMAKEDIR) - -install( - FILES cmake/install-config.cmake - DESTINATION "${async-mqtt5_INSTALL_CMAKEDIR}" - RENAME "${package}Config.cmake" - COMPONENT async-mqtt5_Development -) - -install( - FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" - DESTINATION "${async-mqtt5_INSTALL_CMAKEDIR}" - COMPONENT async-mqtt5_Development -) - -install( - EXPORT async-mqtt5Targets - NAMESPACE Async:: - DESTINATION "${async-mqtt5_INSTALL_CMAKEDIR}" - COMPONENT async-mqtt5_Development -) - -if(PROJECT_IS_TOP_LEVEL) - include(CPack) -endif() diff --git a/cmake/variables.cmake b/cmake/variables.cmake deleted file mode 100644 index 016f258..0000000 --- a/cmake/variables.cmake +++ /dev/null @@ -1,28 +0,0 @@ -# ---- Developer mode ---- - -# Developer mode enables targets and code paths in the CMake scripts that are -# only relevant for the developer(s) of async-mqtt5 -# Targets necessary to build the project must be provided unconditionally, so -# consumers can trivially build and package the project -if(PROJECT_IS_TOP_LEVEL) - option(async-mqtt5_DEVELOPER_MODE "Enable developer mode" OFF) -endif() - -# ---- Warning guard ---- - -# target_include_directories with the SYSTEM modifier will request the compiler -# to omit warnings from the provided paths, if the compiler supports that -# This is to provide a user experience similar to find_package when -# add_subdirectory or FetchContent is used to consume this project -set(warning_guard "") -if(NOT PROJECT_IS_TOP_LEVEL) - option( - async-mqtt5_INCLUDES_WITH_SYSTEM - "Use SYSTEM modifier for async-mqtt5's includes, disabling warnings" - ON - ) - mark_as_advanced(async-mqtt5_INCLUDES_WITH_SYSTEM) - if(async-mqtt5_INCLUDES_WITH_SYSTEM) - set(warning_guard SYSTEM) - endif() -endif() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 16993ce..154b9f7 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,16 +1,17 @@ -cmake_minimum_required(VERSION 3.15) +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# -project(async-mqtt5-examples CXX) +cmake_minimum_required(VERSION 3.8...3.20) -include(../cmake/project-is-top-level.cmake) - -if(PROJECT_IS_TOP_LEVEL) - find_package(async-mqtt5 REQUIRED) -endif() +project(async-mqtt5-examples LANGUAGES CXX) function(add_example name) add_executable("${name}" ${ARGN}) - target_link_libraries("${name}" PRIVATE Async::MQTT5) + target_link_libraries("${name}" PRIVATE Boost::mqtt5) string(FIND "${example_name}" "tls" found_tls) if(found_tls GREATER -1) target_link_libraries("${name}" PRIVATE OpenSSL::SSL) diff --git a/example/hello_world_in_coro_multithreaded_env.cpp b/example/hello_world_in_coro_multithreaded_env.cpp index d0f3609..175deaf 100644 --- a/example/hello_world_in_coro_multithreaded_env.cpp +++ b/example/hello_world_in_coro_multithreaded_env.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include diff --git a/example/hello_world_in_multithreaded_env.cpp b/example/hello_world_in_multithreaded_env.cpp index 2aa36d8..dbc481e 100644 --- a/example/hello_world_in_multithreaded_env.cpp +++ b/example/hello_world_in_multithreaded_env.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include diff --git a/include/boost/mqtt5/detail/async_mutex.hpp b/include/boost/mqtt5/detail/async_mutex.hpp index d2ed84b..77abf61 100644 --- a/include/boost/mqtt5/detail/async_mutex.hpp +++ b/include/boost/mqtt5/detail/async_mutex.hpp @@ -20,9 +20,7 @@ #include #include -#include #include -#include namespace boost::mqtt5::detail { diff --git a/include/boost/mqtt5/detail/control_packet.hpp b/include/boost/mqtt5/detail/control_packet.hpp index 8e12d08..5510453 100644 --- a/include/boost/mqtt5/detail/control_packet.hpp +++ b/include/boost/mqtt5/detail/control_packet.hpp @@ -10,6 +10,7 @@ #include +#include #include #include @@ -109,13 +110,13 @@ public: } qos_e qos() const { - assert(control_code() == control_code_e::publish); + BOOST_ASSERT(control_code() == control_code_e::publish); auto byte = (uint8_t(*(_packet->data())) & 0b00000110) >> 1; return qos_e(byte); } control_packet& set_dup() { - assert(control_code() == control_code_e::publish); + BOOST_ASSERT(control_code() == control_code_e::publish); auto& byte = *(_packet->data()); byte |= 0b00001000; return *this; diff --git a/include/boost/mqtt5/detail/shutdown.hpp b/include/boost/mqtt5/detail/shutdown.hpp index a3fb972..4c8b849 100644 --- a/include/boost/mqtt5/detail/shutdown.hpp +++ b/include/boost/mqtt5/detail/shutdown.hpp @@ -1,3 +1,10 @@ +// +// Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + #ifndef BOOST_MQTT5_SHUTDOWN_HPP #define BOOST_MQTT5_SHUTDOWN_HPP diff --git a/include/boost/mqtt5/impl/assemble_op.hpp b/include/boost/mqtt5/impl/assemble_op.hpp index 7626a02..6b342fc 100644 --- a/include/boost/mqtt5/impl/assemble_op.hpp +++ b/include/boost/mqtt5/impl/assemble_op.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -148,7 +149,7 @@ public: return complete(ec, 0, {}, {}); _data_span.expand_suffix(bytes_read); - assert(_data_span.size()); + BOOST_ASSERT(_data_span.size()); auto control_byte = uint8_t(*_data_span.first()); @@ -187,7 +188,7 @@ private: auto negotiated_ka = _svc.negotiated_keep_alive(); return negotiated_ka ? std::chrono::milliseconds(3 * negotiated_ka * 1000 / 2) : - duration(std::numeric_limits::max()); + duration((std::numeric_limits::max)()); } static bool valid_header(uint8_t control_byte) { diff --git a/include/boost/mqtt5/impl/client_service.hpp b/include/boost/mqtt5/impl/client_service.hpp index acb56ac..6c562de 100644 --- a/include/boost/mqtt5/impl/client_service.hpp +++ b/include/boost/mqtt5/impl/client_service.hpp @@ -277,7 +277,7 @@ private: _replies(_executor), _async_sender(*this), _active_span(_read_buff.cend(), _read_buff.cend()), - _rec_channel(_executor, std::numeric_limits::max()), + _rec_channel(_executor, (std::numeric_limits::max)()), _ping_timer(_executor), _sentry_timer(_executor) { @@ -297,7 +297,7 @@ public: _replies(ex), _async_sender(*this), _active_span(_read_buff.cend(), _read_buff.cend()), - _rec_channel(ex, std::numeric_limits::max()), + _rec_channel(ex, (std::numeric_limits::max)()), _ping_timer(ex), _sentry_timer(ex) {} diff --git a/include/boost/mqtt5/impl/ping_op.hpp b/include/boost/mqtt5/impl/ping_op.hpp index 96c8b6c..88c5832 100644 --- a/include/boost/mqtt5/impl/ping_op.hpp +++ b/include/boost/mqtt5/impl/ping_op.hpp @@ -96,7 +96,7 @@ private: auto negotiated_ka = _svc_ptr->negotiated_keep_alive(); return negotiated_ka ? std::chrono::seconds(negotiated_ka) : - duration(std::numeric_limits::max()); + duration((std::numeric_limits::max)()); } void complete() { diff --git a/include/boost/mqtt5/impl/read_message_op.hpp b/include/boost/mqtt5/impl/read_message_op.hpp index cf8c15e..96a49f5 100644 --- a/include/boost/mqtt5/impl/read_message_op.hpp +++ b/include/boost/mqtt5/impl/read_message_op.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -146,7 +147,7 @@ private: } break; default: - assert(false); + BOOST_ASSERT(false); } perform(); diff --git a/include/boost/mqtt5/logger.hpp b/include/boost/mqtt5/logger.hpp index af4f5f6..3de4193 100644 --- a/include/boost/mqtt5/logger.hpp +++ b/include/boost/mqtt5/logger.hpp @@ -222,7 +222,7 @@ private: std::clog << property_name(prop) << ":"; std::clog << "["; - for (auto i = 0; i < val.size(); i++) { + for (size_t i = 0; i < val.size(); i++) { if constexpr (detail::is_pair) std::clog << "(" << val[i].first << "," << val[i].second << ")"; else diff --git a/meta/libraries.json b/meta/libraries.json index 0cd553c..2d1c6ca 100644 --- a/meta/libraries.json +++ b/meta/libraries.json @@ -11,5 +11,10 @@ "Concurrent", "IO" ], + "maintainers": [ + "Ivica Siladić", + "Bruno Iljazović", + "Korina Šimičević" + ], "cxxstd": "17" } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ca846ea..fe8801f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,13 +1,11 @@ -cmake_minimum_required(VERSION 3.15) +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# -project(async-mqtt5-tests CXX) - -include(../cmake/project-is-top-level.cmake) - -if(PROJECT_IS_TOP_LEVEL) - find_package(async-mqtt5 REQUIRED) - enable_testing() -endif() +project(boost_mqtt5_tests CXX) file(GLOB integration_tests "integration/*.cpp") file(GLOB unit_tests "unit/*.cpp") @@ -17,7 +15,31 @@ add_executable(mqtt-test src/run_tests.cpp ${integration_tests} ${unit_tests}) target_include_directories(mqtt-test PRIVATE include) target_compile_definitions(mqtt-test PRIVATE BOOST_TEST_NO_MAIN=1) -find_package(OpenSSL REQUIRED) -target_link_libraries(mqtt-test PRIVATE Async::MQTT5 OpenSSL::SSL) +if(BOOST_MQTT5_MAIN_PROJECT) + find_package(OpenSSL REQUIRED) + target_compile_definitions(mqtt-test PRIVATE BOOST_MQTT5_EXTRA_DEPS=1) + target_link_libraries( + mqtt-test PRIVATE + Boost::mqtt5 + OpenSSL::SSL + ) +else() + target_link_libraries( + mqtt-test PRIVATE + Boost::mqtt5 + Boost::included_unit_test_framework + ) + + # Follow the Boost convention: don't build test targets by default, + # and only when explicitly requested by building target tests + set_target_properties(mqtt-test PROPERTIES EXCLUDE_FROM_ALL ON) + add_dependencies(tests mqtt-test) +endif() + +include(CTest) add_test(NAME mqtt-test COMMAND mqtt-test) + +if (BOOST_MQTT5_PUBLIC_BROKER_TESTS) + set_property(TEST mqtt-test PROPERTY ENVIRONMENT "BOOST_MQTT5_PUBLIC_BROKER_TESTS=1") +endif() diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 0000000..becc2fe --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,28 @@ +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import-search /boost/config/checks ; +import ac ; + +# Use these requirements as both regular and usage requirements across all tests +local requirements = + /boost/mqtt5//boost_mqtt5 + BOOST_ALL_NO_LIB=1 + BOOST_ASIO_NO_DEPRECATED=1 + BOOST_TEST_NO_MAIN=1 + msvc:"/bigobj" + windows:_WIN32_WINNT=0x0601 + /boost/test//included + ; + +run + src/run_tests.cpp + [ glob "unit/*.cpp" ] +: requirements $(requirements) + include +: target-name boost_mqtt5_tests +; diff --git a/test/cmake_b2_test/CMakeLists.txt b/test/cmake_b2_test/CMakeLists.txt new file mode 100644 index 0000000..38e41da --- /dev/null +++ b/test/cmake_b2_test/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +cmake_minimum_required(VERSION 3.8...3.20) + +project(cmake_b2_test LANGUAGES CXX) + +find_package(Boost REQUIRED COMPONENTS headers) +find_package(Threads REQUIRED) + +add_executable(main ../src/quick.cpp) +target_link_libraries(main PRIVATE Boost::headers Threads::Threads) + +include(CTest) +add_test(main main) diff --git a/test/cmake_install_test/CMakeLists.txt b/test/cmake_install_test/CMakeLists.txt new file mode 100644 index 0000000..715f8b4 --- /dev/null +++ b/test/cmake_install_test/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +cmake_minimum_required(VERSION 3.8...3.20) + +project(cmake_install_test LANGUAGES CXX) + +find_package(boost_mqtt5 REQUIRED) + +add_executable(main ../src/quick.cpp) +target_link_libraries(main Boost::mqtt5) + +include(CTest) +add_test(main main) diff --git a/test/cmake_subdir_test/CMakeLists.txt b/test/cmake_subdir_test/CMakeLists.txt new file mode 100644 index 0000000..eaf1ad6 --- /dev/null +++ b/test/cmake_subdir_test/CMakeLists.txt @@ -0,0 +1,98 @@ +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +cmake_minimum_required(VERSION 3.8...3.20) + +project(cmake_subdir_test LANGUAGES CXX) + +add_subdirectory(../../../mqtt5 boostorg/mqtt5) + +# `boostdep --brief mqtt5` +set(deps + # Primary dependencies + + asio + assert + # beast # Optional dependency, boostdep finds it because of websocket.hpp + container + core + endian + fusion + optional + random + range + smart_ptr + spirit + system + type_traits + + # Secondary dependencies + + align + config + context + date_time + throw_exception + bind + container_hash + intrusive + # logic # Beast dependency + mp11 + preprocessor + static_assert + # static_string # Beast dependency + type_index + winapi + move + function_types + functional + mpl + tuple + typeof + utility + array + dynamic_bitset + integer + io + concept_check + conversion + detail + iterator + regex + "function" + phoenix + pool + proto + thread + unordered + variant + variant2 + describe + predef + algorithm + lexical_cast + numeric/conversion + tokenizer + atomic + chrono + exception + ratio +) + +foreach(dep IN LISTS deps) + add_subdirectory(../../../${dep} boostorg/${dep} EXCLUDE_FROM_ALL) +endforeach() + +if (BUILD_TESTING) + add_subdirectory(../../../test boostorg/test EXCLUDE_FROM_ALL) +endif() + +add_executable(main ../src/quick.cpp) +target_link_libraries(main Boost::mqtt5) + +include(CTest) +add_test(main main) diff --git a/test/include/test_common/extra_deps.hpp b/test/include/test_common/extra_deps.hpp new file mode 100644 index 0000000..9d26548 --- /dev/null +++ b/test/include/test_common/extra_deps.hpp @@ -0,0 +1,34 @@ +// +// Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_TEST_EXTRA_DEPS_HPP +#define BOOST_MQTT5_TEST_EXTRA_DEPS_HPP + +#ifdef BOOST_MQTT5_EXTRA_DEPS +#include + +namespace boost::mqtt5 { + +template +struct tls_handshake_type> { + static constexpr auto client = asio::ssl::stream_base::client; + static constexpr auto server = asio::ssl::stream_base::server; +}; + +template +void assign_tls_sni( + const authority_path& ap, + asio::ssl::context& /* ctx */, + asio::ssl::stream& stream +) { + SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); +} + +} // end namespace boost::mqtt5 +#endif // BOOST_MQTT5_EXTRA_DEPS + +#endif // BOOST_MQTT5_TEST_EXTRA_DEPS_HPP diff --git a/test/include/test_common/preconditions.hpp b/test/include/test_common/preconditions.hpp new file mode 100644 index 0000000..2381e74 --- /dev/null +++ b/test/include/test_common/preconditions.hpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MQTT5_TEST_PRECONDITIONS_HPP +#define BOOST_MQTT5_TEST_PRECONDITIONS_HPP + +#include + +#include + +namespace boost::mqtt5::test { + +static std::string safe_getenv(const char* name) { +#ifdef BOOST_MSVC +#pragma warning(push) +#pragma warning(disable : 4996) // MSVC doesn't like getenv +#endif + const char* res = std::getenv(name); +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + return res ? res : ""; +} + +struct env_condition { + std::string env; + boost::test_tools::assertion_result operator()(boost::unit_test::test_unit_id) { + return !safe_getenv(env.c_str()).empty(); + } +}; +static const env_condition public_broker_cond = + env_condition { "BOOST_MQTT5_PUBLIC_BROKER_TESTS" }; + +} // end namespace boost::mqtt5::test + +#endif // BOOST_MQTT5_TEST_PRECONDITIONS_HPP diff --git a/test/include/test_common/test_broker.hpp b/test/include/test_common/test_broker.hpp index 25cf99a..db4815f 100644 --- a/test/include/test_common/test_broker.hpp +++ b/test/include/test_common/test_broker.hpp @@ -65,7 +65,7 @@ public: pending_read& operator=(const pending_read&) = delete; size_t consume(const std::vector& data) { - size_t num_bytes = std::min(_buffer_size, data.size()); + size_t num_bytes = (std::min)(_buffer_size, data.size()); if (num_bytes == 0) return 0; std::memcpy(_buffer_data, data.data(), num_bytes); @@ -169,11 +169,11 @@ public: ); BOOST_TEST(buffers_size == expected.size()); - size_t num_packets = std::min(buffers_size, expected.size()); + size_t num_packets = (std::min)(buffers_size, expected.size()); auto it = asio::buffer_sequence_begin(buffers); for (size_t i = 0; i < num_packets; ++i, ++it) { BOOST_TEST(it->size() == expected[i].size()); - size_t len = std::min(it->size(), expected[i].size()); + size_t len = (std::min)(it->size(), expected[i].size()); if (memcmp(it->data(), expected[i].data(), len)) BOOST_TEST_MESSAGE( concat_strings( diff --git a/test/include/test_common/test_service.hpp b/test/include/test_common/test_service.hpp index e0309e6..c32384c 100644 --- a/test/include/test_common/test_service.hpp +++ b/test/include/test_common/test_service.hpp @@ -19,7 +19,7 @@ #include #include -#include +#include // std::monostate namespace boost::mqtt5::test { diff --git a/test/integration/client.cpp b/test/integration/client.cpp index f10af7e..6f83015 100644 --- a/test/integration/client.cpp +++ b/test/integration/client.cpp @@ -11,39 +11,21 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT #include -#include #include #include #include #include -#include #include -#include #include #include -namespace boost::mqtt5 { +#include "test_common/extra_deps.hpp" +#include "test_common/preconditions.hpp" -template -struct tls_handshake_type> { - static constexpr auto client = asio::ssl::stream_base::client; - static constexpr auto server = asio::ssl::stream_base::server; -}; - -template -void assign_tls_sni( - const authority_path& ap, - asio::ssl::context& /* ctx */, - asio::ssl::stream& stream -) { - SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); -} - -} // end namespace boost::mqtt5 - -BOOST_AUTO_TEST_SUITE(client/*, *boost::unit_test::disabled()*/) +BOOST_AUTO_TEST_SUITE(client, + * boost::unit_test::precondition(boost::mqtt5::test::public_broker_cond)) using namespace boost::mqtt5; namespace asio = boost::asio; @@ -139,6 +121,8 @@ BOOST_AUTO_TEST_CASE(tcp_client_check) { ioc.run(); } +#ifdef BOOST_MQTT5_EXTRA_DEPS + BOOST_AUTO_TEST_CASE(websocket_tcp_client_check) { asio::io_context ioc; @@ -242,7 +226,8 @@ BOOST_AUTO_TEST_CASE(websocket_tls_client_check) { ioc.run(); } +#endif // BOOST_MQTT5_EXTRA_DEPS BOOST_AUTO_TEST_SUITE_END() -#endif +#endif // BOOST_ASIO_HAS_CO_AWAIT diff --git a/test/integration/client_functions.cpp b/test/integration/client_functions.cpp index 74c16c8..0c0a97f 100644 --- a/test/integration/client_functions.cpp +++ b/test/integration/client_functions.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include +#include "test_common/extra_deps.hpp" #include "test_common/message_exchange.hpp" #include "test_common/packet_util.hpp" #include "test_common/test_authenticators.hpp" @@ -32,7 +32,7 @@ using namespace boost::mqtt5; using namespace std::chrono_literals; -BOOST_AUTO_TEST_SUITE(client_functions/*, *boost::unit_test::disabled()*/) +BOOST_AUTO_TEST_SUITE(client_functions) struct shared_test_data { error_code success {}; @@ -83,18 +83,6 @@ BOOST_AUTO_TEST_CASE(create_client_with_execution_context) { BOOST_CHECK(c.get_executor() == ioc.get_executor()); } -void assign_tls_context() { - // Tests if the tls_context function compiles - - asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tls_client); - - mqtt_client< - asio::ssl::stream, asio::ssl::context - > tls_client(ioc.get_executor(), std::move(ctx)); - tls_client.tls_context(); -} - BOOST_FIXTURE_TEST_CASE(assign_credentials, shared_test_data) { std::string client_id = "client_id"; std::string username = "username"; @@ -118,23 +106,6 @@ BOOST_FIXTURE_TEST_CASE(assign_credentials, shared_test_data) { ); } -void assign_credentials_tls_client() { - // Tests if the assign credentials function compiles - - std::string client_id = "client_id"; - std::string username = "username"; - std::string password = "password"; - - asio::io_context ioc; - - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); - - ts.credentials(client_id, username, password); -} - BOOST_FIXTURE_TEST_CASE(assign_will, shared_test_data) { will w("topic", "message"); std::optional will_opt { std::move(w) }; @@ -168,19 +139,6 @@ void assign_authenticator() { c.authenticator(test::test_authenticator()); } -void assign_authenticator_tls_client() { - // Tests if the authenticator function compiles - - asio::io_context ioc; - - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); - - ts.authenticator(test::test_authenticator()); -} - BOOST_FIXTURE_TEST_CASE(assign_keep_alive, shared_test_data) { uint16_t keep_alive = 120; @@ -272,38 +230,6 @@ BOOST_FIXTURE_TEST_CASE(connect_property, shared_connect_prop_test_data) { ); } -BOOST_FIXTURE_TEST_CASE(connect_properties_tls_client, shared_connect_prop_test_data) { - // Tests if the connect_properties function compiles - - asio::io_context ioc; - - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); - - ts.connect_properties(cprops); -} - -BOOST_FIXTURE_TEST_CASE(connect_property_tls_client, shared_connect_prop_test_data) { - // Tests if the connect_property functions compile - - asio::io_context ioc; - - asio::ssl::context ctx(asio::ssl::context::tls_client); - mqtt_client< - asio::ssl::stream, asio::ssl::context - > ts(ioc.get_executor(), std::move(ctx)); - - ts.connect_property(prop::session_expiry_interval, session_expiry_interval); - ts.connect_property(prop::receive_maximum, receive_maximum); - ts.connect_property(prop::maximum_packet_size, maximum_packet_size); - ts.connect_property(prop::topic_alias_maximum, topic_alias_maximum); - ts.connect_property(prop::request_response_information, request_response_information); - ts.connect_property(prop::request_problem_information, request_problem_information); - ts.connect_property(prop::user_property, user_properties); -} - struct shared_connack_prop_test_data { error_code success {}; @@ -382,7 +308,7 @@ void run_test_with_post_fun( timer.async_wait( [&c, fun = std::forward(client_fun)](error_code) { fun(c); - c.cancel(); + c.cancel(); } ); @@ -440,6 +366,82 @@ BOOST_FIXTURE_TEST_CASE(connack_property, shared_connack_prop_test_data) { ); } +#ifdef BOOST_MQTT5_EXTRA_DEPS + +void assign_credentials_tls_client() { + // Tests if the assign credentials function compiles + + std::string client_id = "client_id"; + std::string username = "username"; + std::string password = "password"; + + asio::io_context ioc; + + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); + + ts.credentials(client_id, username, password); +} + +void assign_tls_context() { + // Tests if the tls_context function compiles + + asio::io_context ioc; + asio::ssl::context ctx(asio::ssl::context::tls_client); + + mqtt_client< + asio::ssl::stream, asio::ssl::context + > tls_client(ioc.get_executor(), std::move(ctx)); + tls_client.tls_context(); +} + +void assign_authenticator_tls_client() { + // Tests if the authenticator function compiles + + asio::io_context ioc; + + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); + + ts.authenticator(test::test_authenticator()); +} + +BOOST_FIXTURE_TEST_CASE(connect_properties_tls_client, shared_connect_prop_test_data) { + // Tests if the connect_properties function compiles + + asio::io_context ioc; + + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); + + ts.connect_properties(cprops); +} + +BOOST_FIXTURE_TEST_CASE(connect_property_tls_client, shared_connect_prop_test_data) { + // Tests if the connect_property functions compile + + asio::io_context ioc; + + asio::ssl::context ctx(asio::ssl::context::tls_client); + mqtt_client< + asio::ssl::stream, asio::ssl::context + > ts(ioc.get_executor(), std::move(ctx)); + + ts.connect_property(prop::session_expiry_interval, session_expiry_interval); + ts.connect_property(prop::receive_maximum, receive_maximum); + ts.connect_property(prop::maximum_packet_size, maximum_packet_size); + ts.connect_property(prop::topic_alias_maximum, topic_alias_maximum); + ts.connect_property(prop::request_response_information, request_response_information); + ts.connect_property(prop::request_problem_information, request_problem_information); + ts.connect_property(prop::user_property, user_properties); +} + void connack_property_with_tls_client() { // Tests if the connack_properties & connack_property functions compile @@ -457,5 +459,6 @@ void connack_property_with_tls_client() { return true; }); } +#endif // BOOST_MQTT5_EXTRA_DEPS BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/integration/mqtt_features.cpp b/test/integration/mqtt_features.cpp index c3825e6..6116108 100644 --- a/test/integration/mqtt_features.cpp +++ b/test/integration/mqtt_features.cpp @@ -11,7 +11,6 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT #include -#include #include #include @@ -20,14 +19,15 @@ #include #include #include -#include #include #include #include +#include "test_common/preconditions.hpp" -BOOST_AUTO_TEST_SUITE(mqtt_features/*, *boost::unit_test::disabled()*/) +BOOST_AUTO_TEST_SUITE(mqtt_features, + * boost::unit_test::precondition(boost::mqtt5::test::public_broker_cond)) using namespace boost::mqtt5; namespace asio = boost::asio; @@ -36,9 +36,9 @@ constexpr auto use_nothrow_awaitable = asio::as_tuple(asio::use_awaitable); constexpr auto test_duration = std::chrono::seconds(5); -using stream_type = boost::beast::websocket::stream; +using stream_type = asio::ip::tcp::socket; -constexpr auto broker = "broker.hivemq.com/mqtt"; +constexpr auto broker = "broker.hivemq.com"; constexpr auto connect_wait_dur = std::chrono::milliseconds(200); constexpr auto topic = "async-mqtt5/test"; constexpr auto share_topic = "$share/sharename/async-mqtt5/test"; @@ -64,7 +64,7 @@ asio::awaitable test_manual_use_topic_alias() { auto ex = co_await asio::this_coro::executor; mqtt_client client(ex); - client.brokers(broker, 8000) + client.brokers(broker, 1883) .connect_property(prop::topic_alias_maximum, uint16_t(10)) .async_run(asio::detached); @@ -96,7 +96,7 @@ asio::awaitable test_subscription_identifiers() { auto ex = co_await asio::this_coro::executor; mqtt_client client(ex); - client.brokers(broker, 8000) + client.brokers(broker, 1883) .async_run(asio::detached); publish_props pprops; @@ -137,7 +137,7 @@ asio::awaitable test_shared_subscription() { auto ex = co_await asio::this_coro::executor; mqtt_client client(ex); - client.brokers(broker, 8000) + client.brokers(broker, 1883) .async_run(asio::detached); subscribe_options sub_opts = { .no_local = no_local_e::no }; @@ -172,7 +172,7 @@ asio::awaitable test_user_property() { auto ex = co_await asio::this_coro::executor; mqtt_client client(ex); - client.brokers(broker, 8000) + client.brokers(broker, 1883) .async_run(asio::detached); publish_props pprops; diff --git a/test/integration/ping.cpp b/test/integration/ping.cpp index 6256aa0..c280770 100644 --- a/test/integration/ping.cpp +++ b/test/integration/ping.cpp @@ -59,7 +59,7 @@ std::string connack_with_keep_alive(uint16_t keep_alive) { void run_test( test::msg_exchange broker_side, std::chrono::milliseconds cancel_timeout, - uint16_t keep_alive = std::numeric_limits::max() + uint16_t keep_alive = (std::numeric_limits::max)() ) { asio::io_context ioc; auto executor = ioc.get_executor(); diff --git a/test/integration/sub_unsub.cpp b/test/integration/sub_unsub.cpp index 209e5dd..0a3f33e 100644 --- a/test/integration/sub_unsub.cpp +++ b/test/integration/sub_unsub.cpp @@ -93,7 +93,7 @@ void run_test(test::msg_exchange broker_side) { ++handlers_called; BOOST_TEST(!ec); - BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST_REQUIRE(rcs.size() == 1u); BOOST_TEST(rcs[0] == reason_codes::granted_qos_0); c.cancel(); @@ -106,7 +106,7 @@ void run_test(test::msg_exchange broker_side) { ++handlers_called; BOOST_TEST(!ec); - BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST_REQUIRE(rcs.size() == 1u); BOOST_TEST(rcs[0] == reason_codes::success); c.cancel(); @@ -423,7 +423,7 @@ void run_cancellation_test(test::msg_exchange broker_side) { ++handlers_called; BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST_REQUIRE(rcs.size() == 1u); BOOST_TEST(rcs[0] == reason_codes::empty); c.cancel(); @@ -439,7 +439,7 @@ void run_cancellation_test(test::msg_exchange broker_side) { ++handlers_called; BOOST_TEST(ec == asio::error::operation_aborted); - BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST_REQUIRE(rcs.size() == 1u); BOOST_TEST(rcs[0] == reason_codes::empty); c.cancel(); diff --git a/test/src/quick.cpp b/test/src/quick.cpp new file mode 100644 index 0000000..aff278c --- /dev/null +++ b/test/src/quick.cpp @@ -0,0 +1,17 @@ +// +// Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +#include +#include + +int main() { + boost::asio::system_executor sys; + boost::mqtt5::mqtt_client client(sys); + return 0; +} diff --git a/test/unit/default_completion_tokens.cpp b/test/unit/default_completion_tokens.cpp index 6f16c19..8452291 100644 --- a/test/unit/default_completion_tokens.cpp +++ b/test/unit/default_completion_tokens.cpp @@ -5,43 +5,23 @@ // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include #include #ifdef BOOST_ASIO_HAS_CO_AWAIT #include -#include -#include #include #include -#include -#include #include #include #include // std::monostate #include -namespace boost::mqtt5 { +#include "test_common/extra_deps.hpp" -namespace asio = boost::asio; - -template -struct tls_handshake_type> { - static constexpr auto client = asio::ssl::stream_base::client; - static constexpr auto server = asio::ssl::stream_base::server; -}; - -template -void assign_tls_sni( - const authority_path& /* ap */, - asio::ssl::context& /* ctx */, - asio::ssl::stream& /* stream */ -) {} - -namespace test { +namespace boost::mqtt5::test { // the following code needs to compile @@ -83,11 +63,13 @@ asio::awaitable test_default_completion_tokens_impl( auto dc_props = disconnect_props {}; co_await c.async_disconnect(); co_await c.async_disconnect(disconnect_rc_e::normal_disconnection, dc_props); + co_return; } asio::awaitable test_default_completion_tokens() { co_await test_default_completion_tokens_impl(); + #ifdef BOOST_MQTT5_EXTRA_DEPS co_await test_default_completion_tokens_impl< asio::ssl::stream, asio::ssl::context @@ -105,10 +87,9 @@ asio::awaitable test_default_completion_tokens() { asio::ssl::context(asio::ssl::context::tls_client), logger(log_level::debug) ); + #endif // BOOST_MQTT5_EXTRA_DEPS } -} // end namespace test - -} // end namespace boost::mqtt5 +} // end namespace boost::mqtt5::test #endif // BOOST_ASIO_HAS_CO_AWAIT diff --git a/test/unit/logger.cpp b/test/unit/logger.cpp index 5e9dc25..236fafa 100644 --- a/test/unit/logger.cpp +++ b/test/unit/logger.cpp @@ -8,15 +8,12 @@ #include #include #include -#include #include #include #include #include -#include -#include #include #include @@ -25,33 +22,15 @@ #include #include +#include "test_common/extra_deps.hpp" #include "test_common/message_exchange.hpp" +#include "test_common/preconditions.hpp" #include "test_common/test_service.hpp" #include "test_common/test_stream.hpp" using namespace boost::mqtt5; namespace asio = boost::asio; -namespace boost::mqtt5 { - -template -struct tls_handshake_type> { - static constexpr auto client = asio::ssl::stream_base::client; - static constexpr auto server = asio::ssl::stream_base::server; -}; - -template -void assign_tls_sni( - const authority_path& ap, - asio::ssl::context& /* ctx */, - asio::ssl::stream& stream -) { - SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str()); -} - -} // end namespace boost::mqtt5 - - void logger_test() { BOOST_STATIC_ASSERT(has_at_resolve); BOOST_STATIC_ASSERT(has_at_tcp_connect); @@ -61,13 +40,6 @@ void logger_test() { BOOST_STATIC_ASSERT(has_at_disconnect); } -using stream_type = boost::beast::websocket::stream< - asio::ssl::stream ->; -using context_type = asio::ssl::context; -using logger_type = logger; -using client_type = mqtt_client; - BOOST_AUTO_TEST_SUITE(logger_tests) class clog_redirect { @@ -90,61 +62,6 @@ bool contains(const std::string& str, const std::string& substr) { return str.find(substr) != std::string::npos; } -BOOST_AUTO_TEST_CASE(successful_connect_debug) { - boost::test_tools::output_test_stream output; - - { - clog_redirect guard(output.rdbuf()); - asio::io_context ioc; - - asio::ssl::context tls_context(asio::ssl::context::tls_client); - client_type c( - ioc, std::move(tls_context), logger(log_level::debug) - ); - - c.brokers("broker.hivemq.com/mqtt", 8884) - .async_run(asio::detached); - - c.async_disconnect([](error_code) {}); - - ioc.run(); - } - - std::string log = output.rdbuf()->str(); - BOOST_TEST_MESSAGE(log); - BOOST_TEST_WARN(contains(log, "resolve")); - BOOST_TEST_WARN(contains(log, "TCP connect")); - BOOST_TEST_WARN(contains(log, "TLS handshake")); - BOOST_TEST_WARN(contains(log, "WebSocket handshake")); - BOOST_TEST_WARN(contains(log, "connack")); -} - -BOOST_AUTO_TEST_CASE(successful_connect_warning) { - boost::test_tools::output_test_stream output; - - { - clog_redirect guard(output.rdbuf()); - - asio::io_context ioc; - asio::ssl::context tls_context(asio::ssl::context::tls_client); - client_type c( - ioc, std::move(tls_context), logger(log_level::warning) - ); - - c.brokers("broker.hivemq.com/mqtt", 8884) - .async_run(asio::detached); - - c.async_disconnect([](error_code) {}); - - ioc.run(); - } - - // If connection is successful, nothing should be printed. - // However if the Broker is down or overloaded, this will cause logs to be printed. - // We should not fail the test because of it. - BOOST_TEST_WARN(output.is_empty()); -} - BOOST_AUTO_TEST_CASE(disconnect) { using test::after; using namespace std::chrono_literals; @@ -194,4 +111,72 @@ BOOST_AUTO_TEST_CASE(disconnect) { BOOST_TEST(contains(log, "disconnect")); } +#ifdef BOOST_MQTT5_EXTRA_DEPS +using stream_type = boost::beast::websocket::stream< + asio::ssl::stream +>; +using context_type = asio::ssl::context; +using logger_type = logger; +using client_type = mqtt_client; + +BOOST_AUTO_TEST_CASE(successful_connect_debug, + * boost::unit_test::precondition(test::public_broker_cond)) +{ + boost::test_tools::output_test_stream output; + + { + clog_redirect guard(output.rdbuf()); + asio::io_context ioc; + + asio::ssl::context tls_context(asio::ssl::context::tls_client); + client_type c( + ioc, std::move(tls_context), logger(log_level::debug) + ); + + c.brokers("broker.hivemq.com/mqtt", 8884) + .async_run(asio::detached); + + c.async_disconnect([](error_code) {}); + + ioc.run(); + } + + std::string log = output.rdbuf()->str(); + BOOST_TEST_MESSAGE(log); + BOOST_TEST_WARN(contains(log, "resolve")); + BOOST_TEST_WARN(contains(log, "TCP connect")); + BOOST_TEST_WARN(contains(log, "TLS handshake")); + BOOST_TEST_WARN(contains(log, "WebSocket handshake")); + BOOST_TEST_WARN(contains(log, "connack")); +} + +BOOST_AUTO_TEST_CASE(successful_connect_warning, + * boost::unit_test::precondition(test::public_broker_cond)) +{ + boost::test_tools::output_test_stream output; + + { + clog_redirect guard(output.rdbuf()); + + asio::io_context ioc; + asio::ssl::context tls_context(asio::ssl::context::tls_client); + client_type c( + ioc, std::move(tls_context), logger(log_level::warning) + ); + + c.brokers("broker.hivemq.com/mqtt", 8884) + .async_run(asio::detached); + + c.async_disconnect([](error_code) {}); + + ioc.run(); + } + + // If connection is successful, nothing should be printed. + // However if the Broker is down or overloaded, this will cause logs to be printed. + // We should not fail the test because of it. + BOOST_TEST_WARN(output.is_empty()); +} +#endif // BOOST_MQTT5_EXTRA_DEPS + BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/serialization.cpp b/test/unit/serialization.cpp index 8ec6bd9..41f4e5a 100644 --- a/test/unit/serialization.cpp +++ b/test/unit/serialization.cpp @@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(test_connect) { BOOST_TEST(*cprops_[prop::topic_alias_maximum] == topic_alias_max); BOOST_TEST(*cprops_[prop::request_response_information] == request_response_information); BOOST_TEST(*cprops_[prop::request_problem_information] == request_problem_information); - BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1u); BOOST_TEST(cprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(cprops_[prop::user_property][0].second == user_property_2); BOOST_TEST(*cprops_[prop::authentication_method] == auth_method); @@ -125,7 +125,7 @@ BOOST_AUTO_TEST_CASE(test_connect) { BOOST_TEST(*(will)[prop::content_type] == will_content_type); BOOST_TEST(*(will)[prop::response_topic] == will_response_topic); BOOST_TEST(*(will)[prop::correlation_data] == will_correlation_data); - BOOST_TEST_REQUIRE((will)[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE((will)[prop::user_property].size() == 1u); BOOST_TEST((will)[prop::user_property][0].first == will_user_property_1); BOOST_TEST((will)[prop::user_property][0].second == will_user_property_2); } @@ -196,7 +196,7 @@ BOOST_AUTO_TEST_CASE(test_connack) { BOOST_TEST(*cprops_[prop::assigned_client_identifier] == assigned_client_id); BOOST_TEST(*cprops_[prop::topic_alias_maximum] == topic_alias_max); BOOST_TEST(*cprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(cprops_[prop::user_property].size() == 1u); BOOST_TEST(cprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(cprops_[prop::user_property][0].second == user_property_2); BOOST_TEST(*cprops_[prop::wildcard_subscription_available] == wildcard_sub); @@ -261,10 +261,10 @@ BOOST_AUTO_TEST_CASE(test_publish) { BOOST_TEST(*pprops_[prop::topic_alias] == topic_alias); BOOST_TEST(*pprops_[prop::response_topic] == response_topic); BOOST_TEST(*pprops_[prop::correlation_data] == correlation_data); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1u); BOOST_TEST(pprops_[prop::user_property][0].first == publish_prop_1); BOOST_TEST(pprops_[prop::user_property][0].second == publish_prop_2); - BOOST_TEST_REQUIRE(pprops_[prop::subscription_identifier].size() == 1); + BOOST_TEST_REQUIRE(pprops_[prop::subscription_identifier].size() == 1u); BOOST_TEST(pprops_[prop::subscription_identifier][0] == subscription_identifier); BOOST_TEST(*pprops_[prop::content_type] == content_type); } @@ -327,7 +327,7 @@ BOOST_AUTO_TEST_CASE(test_puback) { pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(reason_code_ == reason_code); BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1u); BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } @@ -363,7 +363,7 @@ BOOST_AUTO_TEST_CASE(test_pubrec) { pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(reason_code_ == reason_code); BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1u); BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } @@ -399,7 +399,7 @@ BOOST_AUTO_TEST_CASE(test_pubrel) { pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(reason_code_ == reason_code); BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1u); BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } @@ -435,7 +435,7 @@ BOOST_AUTO_TEST_CASE(test_pubcomp) { pprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(reason_code_ == reason_code); BOOST_TEST(*pprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(pprops_[prop::user_property].size() == 1u); BOOST_TEST(pprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(pprops_[prop::user_property][0].second == user_property_2); } @@ -489,7 +489,7 @@ BOOST_AUTO_TEST_CASE(test_subscribe) { sprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(*sprops_[prop::subscription_identifier] == sub_id); - BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1u); BOOST_TEST(sprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(sprops_[prop::user_property][0].second == user_property_2); } @@ -525,7 +525,7 @@ BOOST_AUTO_TEST_CASE(test_suback) { sprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(*sprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(sprops_[prop::user_property].size() == 1u); BOOST_TEST(sprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(sprops_[prop::user_property][0].second == user_property_2); } @@ -558,7 +558,7 @@ BOOST_AUTO_TEST_CASE(test_unsubscribe) { BOOST_TEST(topics_ == topics); uprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); - BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1u); BOOST_TEST(uprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(uprops_[prop::user_property][0].second == user_property_2); } @@ -594,7 +594,7 @@ BOOST_AUTO_TEST_CASE(test_unsuback) { uprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(*uprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(uprops_[prop::user_property].size() == 1u); BOOST_TEST(uprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(uprops_[prop::user_property][0].second == user_property_2); } @@ -631,7 +631,7 @@ BOOST_AUTO_TEST_CASE(test_disconnect) { dprops_.visit([](const auto& p, const auto&) { (void)p; BOOST_TEST_REQUIRE(p); return true; }); BOOST_TEST(*dprops_[prop::session_expiry_interval] == session_expiry_interval); BOOST_TEST(*dprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(dprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(dprops_[prop::user_property].size() == 1u); BOOST_TEST(dprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(dprops_[prop::user_property][0].second == user_property_2); BOOST_TEST(*dprops_[prop::server_reference] == server_reference); @@ -671,7 +671,7 @@ BOOST_AUTO_TEST_CASE(test_auth) { BOOST_TEST(*aprops_[prop::authentication_method] == authentication_method); BOOST_TEST(*aprops_[prop::authentication_data] == authentication_data); BOOST_TEST(*aprops_[prop::reason_string] == reason_string); - BOOST_TEST_REQUIRE(aprops_[prop::user_property].size() == 1); + BOOST_TEST_REQUIRE(aprops_[prop::user_property].size() == 1u); BOOST_TEST(aprops_[prop::user_property][0].first == user_property_1); BOOST_TEST(aprops_[prop::user_property][0].second == user_property_2); } @@ -730,7 +730,7 @@ BOOST_AUTO_TEST_CASE(empty_user_property) { const auto& [topic_, packet_id_, flags, pprops_, payload_] = *rv; auto user_props_ = pprops_[prop::user_property]; - BOOST_TEST_REQUIRE(user_props_.size() == 1); + BOOST_TEST_REQUIRE(user_props_.size() == 1u); BOOST_TEST(user_props_[0].first == ""); BOOST_TEST(user_props_[0].second == ""); } @@ -753,7 +753,7 @@ BOOST_AUTO_TEST_CASE(deserialize_user_property) { const auto& [reason_code_, pprops_] = *rv; auto user_props_ = pprops_[prop::user_property]; - BOOST_TEST_REQUIRE(user_props_.size() == 1); + BOOST_TEST_REQUIRE(user_props_.size() == 1u); BOOST_TEST(user_props_[0].first == "key"); BOOST_TEST(user_props_[0].second == "val"); } @@ -776,7 +776,7 @@ BOOST_AUTO_TEST_CASE(deserialize_empty_user_property) { const auto& [reason_code_, pprops_] = *rv; auto user_props_ = pprops_[prop::user_property]; - BOOST_TEST_REQUIRE(user_props_.size() == 1); + BOOST_TEST_REQUIRE(user_props_.size() == 1u); BOOST_TEST(user_props_[0].first == ""); BOOST_TEST(user_props_[0].second == ""); } diff --git a/test/unit/subscribe_op.cpp b/test/unit/subscribe_op.cpp index f4f13f4..4585778 100644 --- a/test/unit/subscribe_op.cpp +++ b/test/unit/subscribe_op.cpp @@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) { auto handler = [&handlers_called](error_code ec, std::vector rcs, suback_props) { ++handlers_called; BOOST_TEST(ec == client::error::pid_overrun); - BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST_REQUIRE(rcs.size() == 1u); BOOST_TEST(rcs[0] == reason_codes::empty); }; @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(large_subscription_id) { cprops[prop::subscription_identifier_available] = uint8_t(1); subscribe_props sprops; - sprops[prop::subscription_identifier] = std::numeric_limits::max(); + sprops[prop::subscription_identifier] = (std::numeric_limits::max)(); run_test(client::error::malformed_packet, "topic", sprops, cprops); } diff --git a/test/unit/traits.cpp b/test/unit/traits.cpp index 991b85f..df40853 100644 --- a/test/unit/traits.cpp +++ b/test/unit/traits.cpp @@ -12,9 +12,7 @@ #include #include -#include #include -#include #include #include @@ -22,6 +20,8 @@ #include #include +#include "test_common/extra_deps.hpp" + using namespace boost::mqtt5; struct good_authenticator { @@ -62,48 +62,15 @@ BOOST_STATIC_ASSERT(detail::is_authenticator); BOOST_STATIC_ASSERT(!detail::is_authenticator); namespace asio = boost::asio; -namespace beast = boost::beast; using tcp_layer = asio::ip::tcp::socket; -using tls_layer = asio::ssl::stream; -using websocket_tcp_layer = beast::websocket::stream; -using websocket_tls_layer = beast::websocket::stream; - BOOST_STATIC_ASSERT(!detail::has_next_layer); -BOOST_STATIC_ASSERT(detail::has_next_layer); -BOOST_STATIC_ASSERT(detail::has_next_layer); -BOOST_STATIC_ASSERT(detail::has_next_layer); - BOOST_STATIC_ASSERT(!detail::has_tls_layer); -BOOST_STATIC_ASSERT(detail::has_tls_layer); -BOOST_STATIC_ASSERT(!detail::has_tls_layer); -BOOST_STATIC_ASSERT(detail::has_tls_layer); - BOOST_STATIC_ASSERT(!detail::has_tls_handshake); -BOOST_STATIC_ASSERT(detail::has_tls_handshake); -BOOST_STATIC_ASSERT(!detail::has_tls_handshake); -BOOST_STATIC_ASSERT(!detail::has_tls_handshake); - BOOST_STATIC_ASSERT(!detail::has_ws_handshake); -BOOST_STATIC_ASSERT(!detail::has_ws_handshake); -BOOST_STATIC_ASSERT(detail::has_ws_handshake); -BOOST_STATIC_ASSERT(detail::has_ws_handshake); - -BOOST_STATIC_ASSERT(!detail::has_next_layer); -BOOST_STATIC_ASSERT(detail::has_next_layer); -BOOST_STATIC_ASSERT(detail::has_next_layer); -BOOST_STATIC_ASSERT(detail::has_next_layer); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); -BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); -BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); -BOOST_STATIC_ASSERT(std::is_same_v, tls_layer>); - BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); -BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); -BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); -BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); void tcp_layers_test() { asio::system_executor ex; @@ -116,6 +83,34 @@ void tcp_layers_test() { BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); } +#ifdef BOOST_MQTT5_EXTRA_DEPS + +namespace beast = boost::beast; +using tls_layer = asio::ssl::stream; +using websocket_tcp_layer = beast::websocket::stream; +using websocket_tls_layer = beast::websocket::stream; + +BOOST_STATIC_ASSERT(detail::has_next_layer); +BOOST_STATIC_ASSERT(detail::has_tls_layer); +BOOST_STATIC_ASSERT(detail::has_tls_handshake); +BOOST_STATIC_ASSERT(!detail::has_ws_handshake); +BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); +BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + +BOOST_STATIC_ASSERT(detail::has_next_layer); +BOOST_STATIC_ASSERT(!detail::has_tls_layer); +BOOST_STATIC_ASSERT(!detail::has_tls_handshake); +BOOST_STATIC_ASSERT(detail::has_ws_handshake); +BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); +BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + +BOOST_STATIC_ASSERT(detail::has_next_layer); +BOOST_STATIC_ASSERT(detail::has_tls_layer); +BOOST_STATIC_ASSERT(!detail::has_tls_handshake); +BOOST_STATIC_ASSERT(detail::has_ws_handshake); +BOOST_STATIC_ASSERT(std::is_same_v, tls_layer>); +BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); + void tls_layers_test() { asio::system_executor ex; asio::ssl::context ctx(asio::ssl::context::tls_client); @@ -150,3 +145,5 @@ void websocket_tls_layers_test() { detail::lowest_layer_type& llayer = detail::lowest_layer(layer); BOOST_STATIC_ASSERT(std::is_same_v, tcp_layer>); } + +#endif // BOOST_MQTT5_EXTRA_DEPS diff --git a/test/unit/unsubscribe_op.cpp b/test/unit/unsubscribe_op.cpp index 61f553c..a2eac1b 100644 --- a/test/unit/unsubscribe_op.cpp +++ b/test/unit/unsubscribe_op.cpp @@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) { auto handler = [&handlers_called](error_code ec, std::vector rcs, unsuback_props) { ++handlers_called; BOOST_TEST(ec == client::error::pid_overrun); - BOOST_TEST_REQUIRE(rcs.size() == 1); + BOOST_TEST_REQUIRE(rcs.size() == 1u); BOOST_TEST(rcs[0] == reason_codes::empty); }; diff --git a/tools/ci.py b/tools/ci.py new file mode 100644 index 0000000..4d7ef85 --- /dev/null +++ b/tools/ci.py @@ -0,0 +1,398 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt) +# +# Contains commands that are invoked by the CI scripts. +# Having this as a Python file makes it platform-independent. + +from pathlib import Path +from typing import List, Union +import subprocess +import os +import stat +from shutil import rmtree, copytree, ignore_patterns +import argparse + + +# Variables +_is_windows = os.name == 'nt' +_home = Path(os.path.expanduser('~')) +_boost_root = _home.joinpath('boost-root') +_b2_distro = _home.joinpath('boost-b2-distro') +_cmake_distro = _home.joinpath('boost-cmake-distro') +_b2_command = str(_boost_root.joinpath('b2')) + + +# Utilities +def _run(args: List[str]) -> None: + print('+ ', args, flush=True) + subprocess.run(args, check=True) + + +def _mkdir_and_cd(path: Path) -> None: + os.makedirs(str(path), exist_ok=True) + os.chdir(str(path)) + + +def _remove_readonly(func, path, _): + os.chmod(path, stat.S_IWRITE) + func(path) + + +# Parses a string into a boolean (for command-line parsing) +def _str2bool(v: Union[bool, str]) -> bool: + if isinstance(v, bool): + return v + elif v == '1': + return True + elif v == '0': + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + + +# Transforms a b2-like toolset into a compiler command suitable +# to be passed to CMAKE_CXX_COMPILER +def _compiler_from_toolset(toolset: str) -> str: + if toolset.startswith('gcc'): + return toolset.replace('gcc', 'g++') + elif toolset.startswith('clang'): + return toolset.replace('clang', 'clang++') + elif toolset.startswith('msvc'): + return 'cl' + else: + return toolset + + +# If we're on the master branch, we should use the Boost superproject master branch. +# Otherwise, use the superproject develop branch. +def _deduce_boost_branch() -> str: + # Are we in GitHub Actions? + if os.environ.get('GITHUB_ACTIONS') is not None: + ci = 'GitHub Actions' + ref = os.environ.get('GITHUB_BASE_REF', '') or os.environ.get('GITHUB_REF', '') + res = 'master' if ref == 'master' or ref.endswith('/master') else 'develop' + elif os.environ.get('DRONE') is not None: + ref = os.environ.get('DRONE_BRANCH', '') + ci = 'Drone' + res = 'master' if ref == 'master' else 'develop' + else: + ci = 'Unknown' + ref = '' + res = 'develop' + + print('+ Found CI {}, ref={}, deduced branch {}'.format(ci, ref, res)) + + return res + + +# Gets Boost (develop or master, depending on the CI branch we're operating on), +# with the required dependencies, and leaves it at _boost_root. Places our library, +# located under source_dir, under $BOOST_ROOT/libs. Also runs the bootstrap script so b2 is usable. +def _setup_boost( + source_dir: Path +) -> None: + assert source_dir.is_absolute() + assert not _boost_root.exists() + lib_dir = _boost_root.joinpath('libs', 'mqtt5') + branch = _deduce_boost_branch() + + # Clone Boost + _run(['git', 'clone', '-b', branch, '--depth', '1', 'https://github.com/boostorg/boost.git', str(_boost_root)]) + os.chdir(str(_boost_root)) + + # Put our library inside boost root + if lib_dir.exists(): + rmtree(str(lib_dir), onerror=_remove_readonly) + copytree( + str(source_dir), + str(lib_dir), + ignore=ignore_patterns('__build*__', '.git') + ) + + # Install Boost dependencies + _run(["git", "config", "submodule.fetchJobs", "8"]) + _run(["git", "submodule", "update", "-q", "--init", "tools/boostdep"]) + _run(["python3", "tools/boostdep/depinst/depinst.py", "--include", "example", "mqtt5"]) + + # Bootstrap + if _is_windows: + _run(['cmd', '/q', '/c', 'bootstrap.bat']) + else: + _run(['bash', 'bootstrap.sh']) + _run([_b2_command, 'headers', '-d0']) + + +# Builds a Boost distribution using ./b2 install, and places it into _b2_distro. +# This emulates a regular Boost distribution, like the ones in releases +def _build_b2_distro( + toolset: str +): + os.chdir(str(_boost_root)) + _run([ + _b2_command, + '--prefix={}'.format(_b2_distro), + '--with-system', + 'toolset={}'.format(toolset), + '-d0', + 'install' + ]) + + +# Builds a Boost distribution using cmake, and places it into _cmake_distro. +# It includes only our library and any dependency. +def _build_cmake_distro( + generator: str, + build_type: str, + cxxstd: str, + toolset: str, +): + _mkdir_and_cd(_boost_root.joinpath('__build_cmake_test__')) + _run([ + 'cmake', + '-G', + generator, + '-DBUILD_TESTING=ON', + '-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)), + '-DCMAKE_BUILD_TYPE={}'.format(build_type), + '-DCMAKE_CXX_STANDARD={}'.format(cxxstd), + '-DBOOST_INCLUDE_LIBRARIES=mqtt5', + '-DCMAKE_INSTALL_PREFIX={}'.format(_cmake_distro), + '-DBoost_VERBOSE=ON', + '-DCMAKE_INSTALL_MESSAGE=NEVER', + '..' + ]) + _run(['cmake', '--build', '.', '--target', 'tests', '--config', build_type]) + _run(['ctest', '--output-on-failure', '--build-config', build_type]) + _run(['cmake', '--build', '.', '--target', 'install', '--config', build_type]) + + +# Builds our examples +def _build_cmake_standalone_examples( + generator: str, + build_type: str, + cxxstd: str, + toolset: str +): + _mkdir_and_cd(_boost_root.joinpath('libs', 'mqtt5', '__build_standalone_examples__')) + _run([ + 'cmake', + '-DBUILD_EXAMPLES=ON', + '-DBoost_INCLUDE_DIR={}'.format(_boost_root), + '-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)), + '-DCMAKE_PREFIX_PATH={}'.format(_b2_distro), + '-DCMAKE_BUILD_TYPE={}'.format(build_type), + '-DCMAKE_CXX_STANDARD={}'.format(cxxstd), + '-G', + generator, + '..' + ]) + _run(['cmake', '--build', '.']) + + +# Builds our CMake tests as a standalone project +# (BOOST_MQTT5_MAIN_PROJECT is ON) and we find_package Boost. +# This ensures that all our test suite is run. +def _build_cmake_standalone_tests( + generator: str, + build_type: str, + cxxstd: str, + toolset: str +): + _mkdir_and_cd(_boost_root.joinpath('libs', 'mqtt5', '__build_standalone_tests__')) + _run([ + 'cmake', + '-DBUILD_TESTING=ON', + '-DBoost_INCLUDE_DIR={}'.format(_boost_root), + '-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)), + '-DCMAKE_PREFIX_PATH={}'.format(_b2_distro), + '-DCMAKE_BUILD_TYPE={}'.format(build_type), + '-DCMAKE_CXX_STANDARD={}'.format(cxxstd), + '-DBOOST_MQTT5_PUBLIC_BROKER_TESTS=ON', + '-G', + generator, + '..' + ]) + _run(['cmake', '--build', '.']) + + +# Runs the tests built in the previous step +def _run_cmake_standalone_tests( + build_type: str +): + os.chdir(str(_boost_root.joinpath('libs', 'mqtt5', '__build_standalone_tests__', 'test'))) + _run([ + 'ctest', + '--output-on-failure', + '--build-config', build_type, + '--no-tests=error' + ]) + + +# Tests that the library can be consumed using add_subdirectory() +def _run_cmake_add_subdirectory_tests( + generator: str, + build_type: str, + cxxstd: str, + toolset: str +): + test_folder = _boost_root.joinpath('libs', 'mqtt5', 'test', 'cmake_subdir_test', '__build') + _mkdir_and_cd(test_folder) + _run([ + 'cmake', + '-G', + generator, + '-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)), + '-DBUILD_TESTING=ON', + '-DCMAKE_BUILD_TYPE={}'.format(build_type), + '-DCMAKE_CXX_STANDARD={}'.format(cxxstd), + '..' + ]) + _run(['cmake', '--build', '.', '--config', build_type]) + _run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error']) + + +# Tests that the library can be consumed using find_package on a distro built by cmake +def _run_cmake_find_package_tests( + generator: str, + build_type: str, + cxxstd: str, + toolset: str +): + _mkdir_and_cd(_boost_root.joinpath('libs', 'mqtt5', 'test', 'cmake_install_test', '__build')) + _run([ + 'cmake', + '-G', + generator, + '-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)), + '-DBUILD_TESTING=ON', + '-DCMAKE_BUILD_TYPE={}'.format(build_type), + '-DCMAKE_CXX_STANDARD={}'.format(cxxstd), + '-DCMAKE_PREFIX_PATH={}'.format(_cmake_distro), + '..' + ]) + _run(['cmake', '--build', '.', '--config', build_type]) + _run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error']) + + +# Tests that the library can be consumed using find_package on a distro built by b2 +def _run_cmake_b2_find_package_tests( + generator: str, + build_type: str, + cxxstd: str, + toolset: str +): + _mkdir_and_cd(_boost_root.joinpath('libs', 'mqtt5', 'test', 'cmake_b2_test', '__build')) + _run([ + 'cmake', + '-G', + generator, + '-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)), + '-DBUILD_TESTING=ON', + '-DCMAKE_PREFIX_PATH={}'.format(_b2_distro), + '-DCMAKE_BUILD_TYPE={}'.format(build_type), + '-DCMAKE_CXX_STANDARD={}'.format(cxxstd), + '..' + ]) + _run(['cmake', '--build', '.', '--config', build_type]) + _run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error']) + + +# Builds and runs the library tests using b2 +def _run_b2_tests( + variant: str, + cxxstd: str, + toolset: str +): + os.chdir(str(_boost_root)) + _run([ + _b2_command, + '--abbreviate-paths', + 'toolset={}'.format(toolset), + 'cxxstd={}'.format(cxxstd), + 'variant={}'.format(variant), + '-j4', + 'libs/mqtt5/test' + ]) + + +def main(): + # Command line parsing + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + + subp = subparsers.add_parser('setup-boost') + subp.add_argument('--source-dir', type=Path, required=True) + subp.set_defaults(func=_setup_boost) + + subp = subparsers.add_parser('build-b2-distro') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_build_b2_distro) + + subp = subparsers.add_parser('build-cmake-distro') + subp.add_argument('--generator', default='Unix Makefiles') + subp.add_argument('--build-type', default='Debug') + subp.add_argument('--cxxstd', default='20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_build_cmake_distro) + + subp = subparsers.add_parser('build-cmake-standalone-examples') + subp.add_argument('--generator', default='Unix Makefiles') + subp.add_argument('--build-type', default='Debug') + subp.add_argument('--cxxstd', default='20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_build_cmake_standalone_examples) + + subp = subparsers.add_parser('build-cmake-standalone-tests') + subp.add_argument('--generator', default='Unix Makefiles') + subp.add_argument('--build-type', default='Debug') + subp.add_argument('--cxxstd', default='20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_build_cmake_standalone_tests) + + subp = subparsers.add_parser('run-cmake-standalone-tests') + subp.add_argument('--build-type', default='Debug') + subp.set_defaults(func=_run_cmake_standalone_tests) + + subp = subparsers.add_parser('run-cmake-add-subdirectory-tests') + subp.add_argument('--generator', default='Unix Makefiles') + subp.add_argument('--build-type', default='Debug') + subp.add_argument('--cxxstd', default='20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_run_cmake_add_subdirectory_tests) + + subp = subparsers.add_parser('run-cmake-find-package-tests') + subp.add_argument('--generator', default='Unix Makefiles') + subp.add_argument('--build-type', default='Debug') + subp.add_argument('--cxxstd', default='20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_run_cmake_find_package_tests) + + subp = subparsers.add_parser('run-cmake-b2-find-package-tests') + subp.add_argument('--generator', default='Unix Makefiles') + subp.add_argument('--build-type', default='Debug') + subp.add_argument('--cxxstd', default='20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_run_cmake_b2_find_package_tests) + + subp = subparsers.add_parser('run-b2-tests') + subp.add_argument('--variant', default='debug,release') + subp.add_argument('--cxxstd', default='17,20') + subp.add_argument('--toolset', default='gcc') + subp.set_defaults(func=_run_b2_tests) + + # Actually parse the arguments + args = parser.parse_args() + + # Invoke the relevant function (as defined by the func default), with + # the command-line arguments the user passed us (we need to get rid + # of the func property to match function signatures) + # This approach is recommended by Python's argparse docs + args.func(**{k: v for k, v in vars(args).items() if k != 'func'}) + + +if __name__ == '__main__': + main()