Add Jamfile, make CMake scripts Boost compliant

Summary: related to T15996

Reviewers: ivica, korina

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33480
This commit is contained in:
Bruno Iljazovic
2025-02-12 15:49:45 +01:00
parent 53fe64d015
commit 7d18f20e56
41 changed files with 1195 additions and 620 deletions

View File

@ -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 }}

View File

@ -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
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)
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)

37
build.jam Normal file
View File

@ -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>include
;
explicit
[ alias boost_mqtt5 : : : : <library>$(boost_dependencies) ]
[ alias all : boost_mqtt5 test ]
;
call-if : boost-library mqtt5
;

View File

@ -1,4 +0,0 @@
include(CTest)
if(BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -1,4 +0,0 @@
include(CMakeFindDependencyMacro)
find_dependency(Boost 1.82)
include("${CMAKE_CURRENT_LIST_DIR}/async-mqtt5Targets.cmake")

View File

@ -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(<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()

View File

@ -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()

View File

@ -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)

View File

@ -21,6 +21,7 @@
#include <boost/asio/strand.hpp>
#include <boost/asio/thread_pool.hpp>
#include <cassert>
#include <iostream>
#include <string>
#include <thread>

View File

@ -18,6 +18,7 @@
#include <boost/asio/strand.hpp>
#include <boost/asio/thread_pool.hpp>
#include <cassert>
#include <iostream>
#include <string>
#include <thread>

View File

@ -20,9 +20,7 @@
#include <boost/asio/require.hpp>
#include <boost/system/error_code.hpp>
#include <atomic>
#include <deque>
#include <mutex>
namespace boost::mqtt5::detail {

View File

@ -10,6 +10,7 @@
#include <boost/mqtt5/types.hpp>
#include <boost/assert.hpp>
#include <boost/smart_ptr/allocate_unique.hpp>
#include <algorithm>
@ -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;

View File

@ -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

View File

@ -21,6 +21,7 @@
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/assert.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
@ -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<duration::rep>::max());
duration((std::numeric_limits<duration::rep>::max)());
}
static bool valid_header(uint8_t control_byte) {

View File

@ -277,7 +277,7 @@ private:
_replies(_executor),
_async_sender(*this),
_active_span(_read_buff.cend(), _read_buff.cend()),
_rec_channel(_executor, std::numeric_limits<size_t>::max()),
_rec_channel(_executor, (std::numeric_limits<size_t>::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<size_t>::max()),
_rec_channel(ex, (std::numeric_limits<size_t>::max)()),
_ping_timer(ex),
_sentry_timer(ex)
{}

View File

@ -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<duration::rep>::max());
duration((std::numeric_limits<duration::rep>::max)());
}
void complete() {

View File

@ -21,6 +21,7 @@
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <boost/assert.hpp>
#include <cstdint>
#include <memory>
@ -146,7 +147,7 @@ private:
}
break;
default:
assert(false);
BOOST_ASSERT(false);
}
perform();

View File

@ -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<decltype(val[i])>)
std::clog << "(" << val[i].first << "," << val[i].second << ")";
else

View File

@ -11,5 +11,10 @@
"Concurrent",
"IO"
],
"maintainers": [
"Ivica Siladić",
"Bruno Iljazović",
"Korina Šimičević"
],
"cxxstd": "17"
}

View File

@ -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()

28
test/Jamfile Normal file
View File

@ -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 =
<library>/boost/mqtt5//boost_mqtt5
<define>BOOST_ALL_NO_LIB=1
<define>BOOST_ASIO_NO_DEPRECATED=1
<define>BOOST_TEST_NO_MAIN=1
<toolset>msvc:<cxxflags>"/bigobj"
<target-os>windows:<define>_WIN32_WINNT=0x0601
<library>/boost/test//included
;
run
src/run_tests.cpp
[ glob "unit/*.cpp" ]
: requirements $(requirements)
<include>include
: target-name boost_mqtt5_tests
;

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 <boost/mqtt5/websocket_ssl.hpp>
namespace boost::mqtt5 {
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
asio::ssl::context& /* ctx */,
asio::ssl::stream<StreamBase>& 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

View File

@ -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 <boost/test/unit_test.hpp>
#include <utility>
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

View File

@ -65,7 +65,7 @@ public:
pending_read& operator=(const pending_read&) = delete;
size_t consume(const std::vector<uint8_t>& 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(

View File

@ -19,7 +19,7 @@
#include <boost/system/error_code.hpp>
#include <cstdint>
#include <variant>
#include <variant> // std::monostate
namespace boost::mqtt5::test {

View File

@ -11,39 +11,21 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/mqtt5.hpp>
#include <boost/mqtt5/websocket_ssl.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
namespace boost::mqtt5 {
#include "test_common/extra_deps.hpp"
#include "test_common/preconditions.hpp"
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
asio::ssl::context& /* ctx */,
asio::ssl::stream<StreamBase>& 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

View File

@ -13,7 +13,6 @@
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
@ -23,6 +22,7 @@
#include <optional>
#include <string>
#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::ip::tcp::socket>, 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::ip::tcp::socket>, 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> 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::ip::tcp::socket>, 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::ip::tcp::socket>, 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::ip::tcp::socket>, 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<TestingClientFun>(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::ip::tcp::socket>, 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::ip::tcp::socket>, 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::ip::tcp::socket>, 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::ip::tcp::socket>, 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::ip::tcp::socket>, 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();

View File

@ -11,7 +11,6 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/mqtt5.hpp>
#include <boost/mqtt5/websocket.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
@ -20,14 +19,15 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <chrono>
#include <cstdint>
#include <string>
#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<asio::ip::tcp::socket>;
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<void> test_manual_use_topic_alias() {
auto ex = co_await asio::this_coro::executor;
mqtt_client<stream_type> 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<void> test_subscription_identifiers() {
auto ex = co_await asio::this_coro::executor;
mqtt_client<stream_type> client(ex);
client.brokers(broker, 8000)
client.brokers(broker, 1883)
.async_run(asio::detached);
publish_props pprops;
@ -137,7 +137,7 @@ asio::awaitable<void> test_shared_subscription() {
auto ex = co_await asio::this_coro::executor;
mqtt_client<stream_type> 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<void> test_user_property() {
auto ex = co_await asio::this_coro::executor;
mqtt_client<stream_type> client(ex);
client.brokers(broker, 8000)
client.brokers(broker, 1883)
.async_run(asio::detached);
publish_props pprops;

View File

@ -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<uint16_t>::max()
uint16_t keep_alive = (std::numeric_limits<uint16_t>::max)()
) {
asio::io_context ioc;
auto executor = ioc.get_executor();

View File

@ -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();

17
test/src/quick.cpp Normal file
View File

@ -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 <boost/mqtt5/mqtt_client.hpp>
#include <boost/asio/system_executor.hpp>
#include <boost/asio/ip/tcp.hpp>
int main() {
boost::asio::system_executor sys;
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(sys);
return 0;
}

View File

@ -5,43 +5,23 @@
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/mqtt5.hpp>
#include <boost/mqtt5/websocket_ssl.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <cstdint>
#include <string>
#include <variant> // std::monostate
#include <vector>
namespace boost::mqtt5 {
#include "test_common/extra_deps.hpp"
namespace asio = boost::asio;
template <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename StreamBase>
void assign_tls_sni(
const authority_path& /* ap */,
asio::ssl::context& /* ctx */,
asio::ssl::stream<StreamBase>& /* stream */
) {}
namespace test {
namespace boost::mqtt5::test {
// the following code needs to compile
@ -83,11 +63,13 @@ asio::awaitable<void> 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<void> test_default_completion_tokens() {
co_await test_default_completion_tokens_impl<asio::ip::tcp::socket>();
#ifdef BOOST_MQTT5_EXTRA_DEPS
co_await test_default_completion_tokens_impl<
asio::ssl::stream<asio::ip::tcp::socket>,
asio::ssl::context
@ -105,10 +87,9 @@ asio::awaitable<void> 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

View File

@ -8,15 +8,12 @@
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/logger_traits.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/websocket_ssl.hpp>
#include <boost/mqtt5/detail/log_invoke.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <boost/test/tools/output_test_stream.hpp>
#include <boost/test/unit_test.hpp>
@ -25,33 +22,15 @@
#include <string>
#include <type_traits>
#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 <typename StreamBase>
struct tls_handshake_type<asio::ssl::stream<StreamBase>> {
static constexpr auto client = asio::ssl::stream_base::client;
static constexpr auto server = asio::ssl::stream_base::server;
};
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
asio::ssl::context& /* ctx */,
asio::ssl::stream<StreamBase>& 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<logger>);
BOOST_STATIC_ASSERT(has_at_tcp_connect<logger>);
@ -61,13 +40,6 @@ void logger_test() {
BOOST_STATIC_ASSERT(has_at_disconnect<logger>);
}
using stream_type = boost::beast::websocket::stream<
asio::ssl::stream<asio::ip::tcp::socket>
>;
using context_type = asio::ssl::context;
using logger_type = logger;
using client_type = mqtt_client<stream_type, context_type, logger_type>;
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<asio::ip::tcp::socket>
>;
using context_type = asio::ssl::context;
using logger_type = logger;
using client_type = mqtt_client<stream_type, context_type, logger_type>;
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();

View File

@ -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 == "");
}

View File

@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) {
auto handler = [&handlers_called](error_code ec, std::vector<reason_code> 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<int32_t>::max();
sprops[prop::subscription_identifier] = (std::numeric_limits<int32_t>::max)();
run_test(client::error::malformed_packet, "topic", sprops, cprops);
}

View File

@ -12,9 +12,7 @@
#include <boost/asio/async_result.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/system_executor.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
@ -22,6 +20,8 @@
#include <string_view>
#include <type_traits>
#include "test_common/extra_deps.hpp"
using namespace boost::mqtt5;
struct good_authenticator {
@ -62,48 +62,15 @@ BOOST_STATIC_ASSERT(detail::is_authenticator<good_authenticator>);
BOOST_STATIC_ASSERT(!detail::is_authenticator<bad_authenticator>);
namespace asio = boost::asio;
namespace beast = boost::beast;
using tcp_layer = asio::ip::tcp::socket;
using tls_layer = asio::ssl::stream<asio::ip::tcp::socket>;
using websocket_tcp_layer = beast::websocket::stream<tcp_layer>;
using websocket_tls_layer = beast::websocket::stream<tls_layer>;
BOOST_STATIC_ASSERT(!detail::has_next_layer<tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<tls_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<websocket_tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_layer<tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_tls_layer<tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_layer<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_tls_layer<websocket_tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_handshake<tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_tls_handshake<tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_handshake<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_handshake<websocket_tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_ws_handshake<tcp_layer>);
BOOST_STATIC_ASSERT(!detail::has_ws_handshake<tls_layer>);
BOOST_STATIC_ASSERT(detail::has_ws_handshake<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_ws_handshake<websocket_tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_next_layer<tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<tls_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<websocket_tls_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<tcp_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<tls_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<websocket_tcp_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<websocket_tls_layer>, tls_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<tcp_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<tls_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<websocket_tcp_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<websocket_tls_layer>, 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<boost::remove_cv_ref_t<decltype(llayer)>, tcp_layer>);
}
#ifdef BOOST_MQTT5_EXTRA_DEPS
namespace beast = boost::beast;
using tls_layer = asio::ssl::stream<asio::ip::tcp::socket>;
using websocket_tcp_layer = beast::websocket::stream<tcp_layer>;
using websocket_tls_layer = beast::websocket::stream<tls_layer>;
BOOST_STATIC_ASSERT(detail::has_next_layer<tls_layer>);
BOOST_STATIC_ASSERT(detail::has_tls_layer<tls_layer>);
BOOST_STATIC_ASSERT(detail::has_tls_handshake<tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_ws_handshake<tls_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<tls_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<tls_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_layer<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_handshake<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_ws_handshake<websocket_tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<websocket_tcp_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<websocket_tcp_layer>, tcp_layer>);
BOOST_STATIC_ASSERT(detail::has_next_layer<websocket_tls_layer>);
BOOST_STATIC_ASSERT(detail::has_tls_layer<websocket_tls_layer>);
BOOST_STATIC_ASSERT(!detail::has_tls_handshake<websocket_tls_layer>);
BOOST_STATIC_ASSERT(detail::has_ws_handshake<websocket_tls_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::next_layer_type<websocket_tls_layer>, tls_layer>);
BOOST_STATIC_ASSERT(std::is_same_v<detail::lowest_layer_type<websocket_tls_layer>, 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<websocket_tls_layer>& llayer = detail::lowest_layer(layer);
BOOST_STATIC_ASSERT(std::is_same_v<boost::remove_cv_ref_t<decltype(llayer)>, tcp_layer>);
}
#endif // BOOST_MQTT5_EXTRA_DEPS

View File

@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(pid_overrun) {
auto handler = [&handlers_called](error_code ec, std::vector<reason_code> 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);
};

398
tools/ci.py Normal file
View File

@ -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()