2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-26 06:52:09 +00:00

Compare commits

...

60 Commits

Author SHA1 Message Date
Marcelo
0380e643ed Merge pull request #157 from boostorg/develop
Latest develop changes
2023-10-08 09:10:07 +02:00
Marcelo
ff734694ab Merge pull request #156 from boostorg/prepare_for_first_boost_release
Prepare for first boost release
2023-10-07 21:51:28 +02:00
Marcelo Zimbres
548e3d4cb6 Updates the copywrite notice. 2023-10-07 16:44:17 +02:00
Marcelo Zimbres
66b632b13d Small fixes in the docs. 2023-10-07 16:40:13 +02:00
Marcelo
11c9c1b787 Merge pull request #155 from anarthal/feature/118-boost-integration
118 boost integration
2023-10-05 22:42:31 +02:00
Ruben Perez
d6f9e435c7 Revert "Fixed libc++ link flags"
This reverts commit 199fb6c261.
2023-10-05 16:55:53 +02:00
Ruben Perez
9a7816dbf4 switched to default installation of openssl 2023-10-05 16:22:59 +02:00
Ruben Perez
199fb6c261 Fixed libc++ link flags 2023-10-05 16:19:24 +02:00
Ruben Perez
4d30d1e0c0 split cmake_test 2023-10-05 16:10:25 +02:00
Ruben Perez
92be6d958f Reduced ci.py verbosity 2023-10-05 16:08:49 +02:00
Ruben Perez
14d3c0232e Removed unnecessary checks fom jamfile 2023-10-05 16:08:32 +02:00
Ruben Perez
7412b37e08 choco => vcpkg 2023-10-05 16:03:32 +02:00
Ruben Perez
60ba5b62af Missing packages in coverage build 2023-10-05 14:13:19 +02:00
Ruben Perez
0303ae0dbc Simplified & documented Jamfile 2023-10-05 13:47:14 +02:00
Ruben Perez
ea6c5536c1 CMAKE_BUILD_PARALLEL_LEVEL for coverage 2023-10-05 13:26:00 +02:00
Ruben Perez
d386b30c3a Simplified ci.py 2023-10-05 13:25:49 +02:00
Ruben Perez
2951acc80f Merge branch 'feature/118-boost-integration' of github.com:anarthal/boost-redis into feature/118-boost-integration 2023-10-05 12:53:52 +02:00
Ruben Perez
faf15fe7e8 Initial coverage workflow 2023-10-05 12:45:35 +02:00
Ruben Perez
7f3f8b0c13 Relaxed cxx17 requirement in Jamfile 2023-10-05 12:33:49 +02:00
Ruben Perez
f37e514961 Link error fix in win b2 2023-10-05 12:02:47 +02:00
Ruben Perez
b7b4f8f449 OpenSSL win fix in CI 2023-10-05 12:02:31 +02:00
Ruben Perez
686cb306ea README now states Boost requirements 2023-10-04 19:13:50 +02:00
Ruben Perez
fcbe2c431c Canonical project name 2023-10-04 19:10:24 +02:00
Ruben Perez
a7b3fbdd9a Protect min/max 2023-10-04 19:06:31 +02:00
Ruben Perez
5ea0d3c467 Fixed OPENSSL_ROOT on win 2023-10-04 19:06:13 +02:00
Ruben Perez
2cd487784b Attempt to solve b2 openssl problem in win 2023-10-04 18:58:07 +02:00
Ruben Perez
b41e2704a1 choco no progress 2023-10-04 18:50:51 +02:00
Ruben Perez
765f0d45e8 Improved CI build names 2023-10-04 18:48:37 +02:00
Ruben Perez
84c8649d66 Bad b2 command 2023-10-04 18:48:29 +02:00
Ruben Perez
0bf4e76981 B2 CI 2023-10-04 18:41:48 +02:00
Ruben Perez
1d329df81b test jamfile 2023-10-04 18:31:47 +02:00
Ruben Perez
56f7d5af69 examples => example 2023-10-04 17:47:03 +02:00
Ruben Perez
d0c3b3f7ee generator fix 2023-10-04 17:38:50 +02:00
Ruben Perez
87ebc6cf4a protobuf fix 2023-10-04 17:38:34 +02:00
Ruben Perez
ffc35e8e3e copytree and cxxstd 2023-10-04 17:23:48 +02:00
Ruben Perez
a02837ab33 Explicit Python & typos 2023-10-04 17:12:11 +02:00
Ruben Perez
4a39a0d20a Toolset 2023-10-04 17:07:26 +02:00
Ruben Perez
56d9a2778f Typo fix 2023-10-04 12:50:41 +02:00
Ruben Perez
c732f33b48 New CI 2023-10-04 12:49:13 +02:00
Ruben Perez
221016f1c9 subdir tests 2023-10-04 12:29:59 +02:00
Ruben Perez
cb9fdba0a4 New cmakes 2023-10-04 11:28:55 +02:00
Ruben Perez
1c96a60709 ci.py first version 2023-10-03 23:09:34 +02:00
Ruben Perez
b66d067af8 tests => test 2023-10-03 23:08:59 +02:00
Ruben Perez
bc08a8d411 Trigger CI 2023-10-03 21:04:43 +02:00
Ruben Perez
53ef947cf3 Doc install and redirection 2023-10-03 18:59:21 +02:00
Ruben Perez
ecfe51c7ae Doc fixes 2023-10-03 17:27:31 +02:00
Ruben Perez
be20c0d48c Docs via b2 2023-10-03 16:51:05 +02:00
Ruben Perez
d5031c3f69 libraries.json 2023-10-02 17:17:44 +02:00
Marcelo
6748f7682a Merge pull request #153 from boostorg/152-enable-reading-server-pushes-in-batches
152 enable reading server pushes in batches
2023-09-10 22:28:28 +02:00
Marcelo Zimbres
2a4936a9e1 Implements batch reads for server pushes. 2023-09-10 12:05:37 +02:00
Marcelo Zimbres
4547e1ac07 First steps with using adapters to process a generic_response. 2023-09-04 14:00:12 +02:00
Marcelo
44a608c0ba Merge pull request #151 from boostorg/150-remove-resp3read-and-resp3async_read
Removes resp3::async_read.
2023-09-02 14:52:36 +02:00
Marcelo Zimbres
1ed8e0182c Removes resp3::async_read. 2023-09-02 13:05:06 +02:00
Marcelo
d8cf431dc2 Merge pull request #149 from boostorg/144-implement-connection-usage-information
Adds connection usage information.
2023-08-30 09:30:30 +02:00
Marcelo Zimbres
401dd24419 Adds connection usage information. 2023-08-29 16:31:23 +02:00
Marcelo
509635f222 Merge pull request #145 from boostorg/138-use-stdfunction-to-type-erase-the-adapter
Uses std::function to type erase the response adapter
2023-08-26 15:39:49 +02:00
Marcelo Zimbres
4fbd0c6853 Progreeses with the adapter type erasure. 2023-08-26 13:09:48 +02:00
Marcelo
b8899ecdc7 Merge pull request #143 from mrichmon/develop
Fix cmake find_package
2023-08-22 08:26:07 +02:00
Michael Richmond
7d09040646 Bump version number 2023-08-21 16:42:41 -07:00
Michael Richmond
0de26fb0ce Fix out of date filename 2023-08-21 16:42:17 -07:00
99 changed files with 2603 additions and 4400 deletions

View File

@@ -7,7 +7,7 @@ codecov:
ignore:
- "benchmarks/cpp/asio/*"
- "examples/*"
- "example/*"
- "tests/*"
- "/usr/*"
- "**/boost/*"

View File

@@ -1,10 +1,18 @@
# CI script to verify that CMake and B2 builds work.
# B2 builds include only tests that don't require a DB server, to avoid race conditions.
# CMake tests include the actual project tests and all the CMake integration workflows
# recommended by Boost.CI.
# Windows CMake jobs build the code but don't run the tests,
# since we don't have a way to set up a Redis server on Windows (yet).
# Subcommands are implemented by the tools/ci.py script in a platform-independent manner.
name: CI
on: [push, pull_request]
jobs:
windows:
name: "${{matrix.generator}} ${{matrix.toolset}} Boost ${{matrix.boost_version}} ${{matrix.build_type}} ${{matrix.name_args}}"
windows-cmake:
name: "CMake ${{matrix.toolset}} ${{matrix.build-type}} C++${{matrix.cxxstd}}"
runs-on: ${{matrix.os}}
defaults:
run:
@@ -12,84 +20,109 @@ jobs:
strategy:
fail-fast: false
matrix:
boost_version: ["1.81.0"]
os: [windows-2019, windows-2022]
toolset: [v142, v143]
build_type: [Release]
generator: ["Visual Studio 16 2019", "Visual Studio 17 2022"]
config_args: [""]
build_args: [""]
name_args: [""]
exclude:
- { os: windows-2019, toolset: v143 }
- { os: windows-2019, generator: "Visual Studio 17 2022" }
- { os: windows-2022, generator: "Visual Studio 16 2019" }
# The following combinations are not available through install-boost
- { boost_version: "1.81.0", toolset: v143 }
include:
- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Debug', build-shared-libs: 1 }
- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Release', build-shared-libs: 0 }
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Debug', build-shared-libs: 0 }
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Release', build-shared-libs: 1 }
env:
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add boost toolset to environment
if: contains(fromJson('["1.81.0"]'), matrix.boost_version)
run: echo BOOST_TOOLSET=$(echo "msvc") >> $GITHUB_ENV
- name: Setup Boost
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
# The platform_version passed to boost-install determines the msvc toolset version for which static libs are installed.
- name: Add boost platform version to environment
- name: Build a Boost distribution using B2
run: |
declare -A toolset_to_platform_version=( [v142]="2019" [v143]="2022" )
key=$(echo "${{matrix.toolset}}")
echo BOOST_PLATFORM_VERSION="${toolset_to_platform_version[$key]}" >> $GITHUB_ENV
python3 tools/ci.py build-b2-distro \
--toolset ${{ matrix.toolset }}
- name: Add boost install path to environment
run: echo BOOST_INSTALL_PATH="${GITHUB_WORKSPACE}/boost-${{matrix.boost_version}}${BOOST_TOOLSET}${BOOST_PLATFORM_VERSION}" >> $GITHUB_ENV
- name: Add build type configuration to environment
run: echo BUILD_CONFIG_ARG="--config ${{matrix.build_type}}" >> $GITHUB_ENV
- name: Cache Boost installation
id: cache-boost
uses: actions/cache@v3
with:
path: ${{env.BOOST_INSTALL_PATH}}
key: ${{matrix.boost_version}}${{env.BOOST_TOOLSET}}${{env.BOOST_PLATFORM_VERSION}}
- name: Install Boost
if: steps.cache-boost.outputs.cache-hit != 'true'
uses: MarkusJx/install-boost@v2.4.1
with:
boost_version: ${{matrix.boost_version}}
toolset: ${{env.BOOST_TOOLSET}}
boost_install_dir: ${{env.BOOST_INSTALL_PATH}}
platform_version: ${{env.BOOST_PLATFORM_VERSION}}
arch: null
- name: Install openssl
run: choco install openssl
- name: Create build directory
run: mkdir build
- name: Configure
working-directory: build
- name: Build a Boost distribution using CMake
run: |
cmake -T "${{matrix.toolset}}" \
-G "${{matrix.generator}}" \
${{matrix.config_args}} \
${BOOST_COMPILER_ARG}\
"${GITHUB_WORKSPACE}"
env:
BOOST_ROOT: ${{env.BOOST_INSTALL_PATH}}/boost
- name: Build
working-directory: build
python3 tools/ci.py build-cmake-distro \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
- name: Build the project tests
run: |
cmake --build . ${BUILD_CONFIG_ARG} ${{matrix.build_args}}
python3 tools/ci.py build-cmake-standalone-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
posix:
# # TODO: re-enable this when a Redis server is available for this job
# - name: Run the project tests
# run: |
# python3 tools/ci.py run-cmake-standalone-tests \
# --build-type ${{ matrix.build-type }}
- name: Run add_subdirectory tests
run: |
python3 tools/ci.py run-cmake-add-subdirectory-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
- name: Run find_package tests with the built cmake distribution
run: |
python3 tools/ci.py run-cmake-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
- name: Run find_package tests with the built b2 distribution
run: |
python3 tools/ci.py run-cmake-b2-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
windows-b2:
name: "B2 ${{matrix.toolset}}"
runs-on: ${{matrix.os}}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- { toolset: msvc-14.2, os: windows-2019 }
- { toolset: msvc-14.3, os: windows-2022 }
env:
OPENSSL_ROOT: "C:\\Program Files\\OpenSSL"
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup user-config.jam
run: cp tools/user-config.jam "${HOMEDRIVE}${HOMEPATH}/"
- name: Setup Boost
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build and run project tests using B2
run: |
python3 tools/ci.py run-b2-tests \
--toolset ${{ matrix.toolset }} \
--cxxstd 17,20 \
--variant debug,release
posix-cmake:
name: "CMake ${{ matrix.toolset }} ${{ matrix.cxxstd }} ${{ matrix.build-type }} ${{ matrix.cxxflags }}"
defaults:
run:
shell: bash
@@ -98,39 +131,100 @@ jobs:
fail-fast: false
matrix:
include:
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++17 -stdlib=libc++', ldflags: '-lc++' }
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++20 -stdlib=libc++', ldflags: '-lc++' }
- { toolset: gcc-11, install: g++-11, os: ubuntu-22.04, cxxstd: '17', build-type: 'Debug', ldflags: '' }
- { toolset: gcc-11, install: g++-11, os: ubuntu-22.04, cxxstd: '20', build-type: 'Release', ldflags: '' }
- { toolset: clang-11, install: clang-11, os: ubuntu-22.04, cxxstd: '17', build-type: 'Debug', ldflags: '' }
- { toolset: clang-11, install: clang-11, os: ubuntu-22.04, cxxstd: '20', build-type: 'Debug', ldflags: '' }
- { toolset: clang-13, install: clang-13, os: ubuntu-22.04, cxxstd: '17', build-type: 'Release', ldflags: '' }
- { toolset: clang-13, install: clang-13, os: ubuntu-22.04, cxxstd: '20', build-type: 'Release', ldflags: '' }
- { toolset: clang-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxstd: '17', build-type: 'Debug', cxxflags: '-stdlib=libc++', ldflags: '-lc++' }
- { toolset: clang-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxstd: '20', build-type: 'Release', cxxflags: '-stdlib=libc++', ldflags: '-lc++' }
runs-on: ${{ matrix.os }}
env:
CXXFLAGS: -g -O0 ${{matrix.cxxflags}} -Wall -Wextra
CXXFLAGS: ${{matrix.cxxflags}} -Wall -Wextra
LDFLAGS: ${{matrix.ldflags}}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install CMake
run: sudo apt-get -y install cmake
- name: Install protobuf
run: sudo apt-get -y install protobuf-compiler
- name: Install compiler
run: sudo apt-get install -y ${{ matrix.install }}
- name: Install Redis
run: sudo apt-get install -y redis-server
- name: Install boost
uses: MarkusJx/install-boost@v2.4.1
id: install-boost
with:
boost_version: 1.81.0
platform_version: 22.04
- name: Run CMake
- name: Install dependencies
run: sudo apt-get -y install cmake protobuf-compiler redis-server python3 ${{ matrix.install }}
- name: Setup Boost
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build a Boost distribution using B2
run: |
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake -DCMAKE_CXX_COMPILER="${{matrix.compiler}}" -DCMAKE_CXX_FLAGS="${{env.CXXFLAGS}}" -DCMAKE_EXE_LINKER_FLAGS="${{env.LDFLAGS}}"
- name: Build
run: make
- name: Check
run: ctest --output-on-failure
./tools/ci.py build-b2-distro \
--toolset ${{ matrix.toolset }}
- name: Build a Boost distribution using CMake
run: |
./tools/ci.py build-cmake-distro \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Build the project tests
run: |
./tools/ci.py build-cmake-standalone-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run the project tests
run: |
./tools/ci.py run-cmake-standalone-tests \
--build-type ${{ matrix.build-type }}
- name: Run add_subdirectory tests
run: |
./tools/ci.py run-cmake-add-subdirectory-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run find_package tests with the built cmake distribution
run: |
./tools/ci.py run-cmake-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run find_package tests with the built b2 distribution
run: |
./tools/ci.py run-cmake-b2-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
posix-b2:
name: "B2 ${{ matrix.toolset }}"
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- { toolset: gcc-11, install: g++-11, cxxstd: "11,17,20" } # Having C++11 shouldn't break the build
- { toolset: clang-14, install: clang-14, cxxstd: "17,20" }
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: sudo apt-get -y install python3 ${{ matrix.install }}
- name: Setup Boost
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build and run project tests using B2
run: |
python3 tools/ci.py run-b2-tests \
--toolset ${{ matrix.toolset }} \
--cxxstd ${{ matrix.cxxstd }} \
--variant debug,release

View File

@@ -4,6 +4,7 @@ on:
push:
branches:
- develop
jobs:
posix:
defaults:
@@ -15,33 +16,36 @@ jobs:
CXX: g++-11
CXXFLAGS: -g -O0 -std=c++20 --coverage -fkeep-inline-functions -fkeep-static-functions
LDFLAGS: --coverage
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install CMake
run: sudo apt-get -y install cmake
- name: Install lcov
run: sudo apt-get -y install lcov
- name: Install compiler
run: sudo apt-get -y install g++-11
- name: Install Redis
run: sudo apt-get -y install redis-server
- name: Install boost
uses: MarkusJx/install-boost@v2.4.1
id: install-boost
with:
boost_version: 1.81.0
platform_version: 22.04
- name: Install dependencies
run: sudo apt-get --no-install-recommends -y install cmake lcov g++-11 redis-server python3 libgd-perl
- name: Setup Boost
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build Boost
run: ./tools/ci.py build-b2-distro --toolset=gcc-11
# Having our library there confuses the coverage reports
- name: Remove Boost.Redis from the b2 distro
run: rm -rf ~/boost-b2-distro/include/boost/redis
- name: Run CMake
run: |
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake --preset coverage .
run: cmake -DCMAKE_PREFIX_PATH=$HOME/boost-b2-distro --preset coverage .
- name: Build
run: cmake --build --preset coverage
- name: Test
run: ctest --preset coverage
- name: Make the coverage file
run: cmake --build --preset coverage --target coverage
- name: Upload to codecov
run: |
bash <(curl -s https://codecov.io/bash) -f ./build/coverage/coverage.info || echo "Codecov did not collect coverage reports"
bash <(curl -s https://codecov.io/bash) -f ./build/coverage/coverage.info

View File

@@ -1,264 +1,78 @@
cmake_minimum_required(VERSION 3.14)
#set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
cmake_minimum_required(VERSION 3.8...3.20)
# determine whether it's main/root project
# or being built under another project.
if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT)
set(BOOST_REDIS_MAIN_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(BOOST_REDIS_MAIN_PROJECT ON)
endif()
set(BOOST_REDIS_MAIN_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(BOOST_REDIS_MAIN_PROJECT ON)
endif()
endif()
project(
boost_redis
VERSION 1.4.1
DESCRIPTION "A redis client library"
HOMEPAGE_URL "https://boostorg.github.io/redis/"
LANGUAGES CXX
)
option(BOOST_REDIS_INSTALL "Generate install targets." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_TESTS "Build tests." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_EXAMPLES "Build examples." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_BENCHMARKS "Build benchmarks." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_DOC "Generate documentations." ${BOOST_REDIS_MAIN_PROJECT})
project(boost_redis VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
# Library
add_library(boost_redis INTERFACE)
add_library(Boost::redis ALIAS boost_redis)
target_include_directories(boost_redis INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(
boost_redis
INTERFACE
Boost::asio
Boost::assert
Boost::config
Boost::core
Boost::mp11
Boost::system
Boost::utility
)
target_include_directories(boost_redis INTERFACE include)
target_compile_features(boost_redis INTERFACE cxx_std_17)
# Asio bases C++ feature detection on __cplusplus. Make MSVC
# define it correctly
if (MSVC)
target_compile_options(boost_redis INTERFACE /Zc:__cplusplus)
endif()
find_package(Boost 1.80 REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
find_package(OpenSSL REQUIRED)
include_directories(include)
# Common
#=======================================================================
add_library(boost_redis_project_options INTERFACE)
target_link_libraries(boost_redis_project_options INTERFACE OpenSSL::Crypto OpenSSL::SSL)
if (MSVC)
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
endif()
add_library(boost_redis_src STATIC examples/boost_redis.cpp)
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
# Executables
#=======================================================================
if (BOOST_REDIS_BENCHMARKS)
add_library(benchmarks_options INTERFACE)
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
endif()
if (BOOST_REDIS_EXAMPLES)
add_library(examples_main STATIC examples/main.cpp)
target_compile_features(examples_main PRIVATE cxx_std_20)
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
macro(make_example EXAMPLE_NAME STANDARD)
add_executable(${EXAMPLE_NAME} examples/${EXAMPLE_NAME}.cpp)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
if (${STANDARD} STREQUAL "20")
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
endif()
endmacro()
macro(make_testable_example EXAMPLE_NAME STANDARD)
make_example(${EXAMPLE_NAME} ${STANDARD})
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
endmacro()
make_testable_example(cpp17_intro 17)
make_testable_example(cpp17_intro_sync 17)
make_testable_example(cpp20_intro 20)
make_testable_example(cpp20_containers 20)
make_testable_example(cpp20_json 20)
make_testable_example(cpp20_intro_tls 20)
make_example(cpp20_subscriber 20)
make_example(cpp20_streams 20)
make_example(cpp20_echo_server 20)
make_example(cpp20_resolve_with_sentinel 20)
# We test the protobuf example only on gcc.
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_package(Protobuf)
if (Protobuf_FOUND)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto)
make_testable_example(cpp20_protobuf 20)
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
endif()
# Dependencies
if (BOOST_REDIS_MAIN_PROJECT)
# If we're the root project, error if a dependency is not found
find_package(Boost 1.83 REQUIRED COMPONENTS headers)
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(boost_redis
INTERFACE
Boost::headers
Threads::Threads
OpenSSL::Crypto
OpenSSL::SSL
)
else()
# If we're in the superproject or called from add_subdirectory,
# Boost dependencies should be already available.
# If other dependencies are not found, we bail out
find_package(Threads)
if(NOT Threads_FOUND)
message(STATUS "Boost.Redis has been disabled, because the required package Threads hasn't been found")
return()
endif()
find_package(OpenSSL)
if(NOT OpenSSL_FOUND)
message(STATUS "Boost.Redis has been disabled, because the required package OpenSSL hasn't been found")
return()
endif()
if (NOT MSVC)
make_example(cpp20_chat_room 20)
endif()
endif()
if (BOOST_REDIS_TESTS)
enable_testing()
add_library(tests_common STATIC tests/common.cpp)
target_compile_features(tests_common PRIVATE cxx_std_17)
target_link_libraries(tests_common PRIVATE boost_redis_project_options)
macro(make_test TEST_NAME STANDARD)
add_executable(${TEST_NAME} tests/${TEST_NAME}.cpp)
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_src tests_common)
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_project_options)
target_compile_features(${TEST_NAME} PRIVATE cxx_std_${STANDARD})
add_test(${TEST_NAME} ${TEST_NAME})
endmacro()
make_test(test_conn_quit 17)
make_test(test_conn_tls 17)
make_test(test_low_level 17)
make_test(test_conn_exec_retry 17)
make_test(test_conn_exec_error 17)
make_test(test_request 17)
make_test(test_run 17)
make_test(test_low_level_sync 17)
make_test(test_low_level_sync_sans_io 17)
make_test(test_conn_check_health 17)
make_test(test_conn_exec 20)
make_test(test_conn_push 20)
make_test(test_conn_reconnect 20)
make_test(test_conn_exec_cancel 20)
make_test(test_conn_exec_cancel2 20)
make_test(test_conn_echo_stress 20)
make_test(test_low_level_async 20)
make_test(test_conn_run_cancel 20)
make_test(test_issue_50 20)
endif()
# Install
#=======================================================================
if (BOOST_REDIS_INSTALL)
install(TARGETS boost_redis
EXPORT boost_redis
PUBLIC_HEADER DESTINATION include COMPONENT Development
)
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/BoostRedisConfig.cmake.in"
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
INSTALL_DESTINATION lib/cmake/boost/redis
)
install(EXPORT boost_redis DESTINATION lib/cmake/boost/redis)
install(FILES "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
DESTINATION lib/cmake/boost/redis)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
COMPATIBILITY AnyNewerVersion
)
include(CPack)
endif()
# Doxygen
#=======================================================================
if (BOOST_REDIS_DOC)
set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc")
configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY)
add_custom_target(
doc
COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile"
COMMENT "Building documentation using Doxygen"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
VERBATIM
# This is generated by boostdep
target_link_libraries(boost_redis
INTERFACE
Boost::asio
Boost::assert
Boost::core
Boost::mp11
Boost::system
Boost::throw_exception
Threads::Threads
OpenSSL::Crypto
OpenSSL::SSL
)
endif()
# Coverage
#=======================================================================
# Enable testing. If we're being called from the superproject, this has already been done
if (BOOST_REDIS_MAIN_PROJECT)
include(CTest)
endif()
set(
COVERAGE_TRACE_COMMAND
lcov --capture
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
--directory "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/include/*"
)
# Most tests require a running Redis server, so we only run them if we're the main project
if(BOOST_REDIS_MAIN_PROJECT AND BUILD_TESTING)
# Tests and common utilities
add_subdirectory(test)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
--prefix "${PROJECT_SOURCE_DIR}"
--output-directory "${PROJECT_BINARY_DIR}/coverage_html"
)
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)
# TODO
#=======================================================================
#.PHONY: bench
#bench:
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
# pdftoppm {input.pdf} {output.file} -png
# Benchmarks. Build them with tests to prevent code rotting
add_subdirectory(benchmarks)
# Examples
add_subdirectory(example)
endif()

View File

@@ -12,7 +12,7 @@
"warnings": {
"dev": true,
"deprecated": true,
"uninitialized": true,
"uninitialized": false,
"unusedCli": true,
"systemVars": false
},
@@ -52,8 +52,7 @@
"CMAKE_CXX_COMPILER": "g++-11",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11",
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11/doc/"
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11"
}
},
{
@@ -69,8 +68,7 @@
"CMAKE_CXX_COMPILER": "g++-11",
"CMAKE_SHARED_LINKER_FLAGS": "",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release",
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-release/doc/"
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release"
}
},
{
@@ -86,8 +84,23 @@
"CMAKE_CXX_COMPILER": "clang++-13",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13",
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/clang++-13/doc/"
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13"
}
},
{
"name": "clang++-14",
"generator": "Unix Makefiles",
"hidden": false,
"inherits": ["cmake-pedantic"],
"binaryDir": "${sourceDir}/build/clang++-14",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
"CMAKE_CXX_COMPILER": "clang++-14",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-14"
}
},
{
@@ -104,8 +117,7 @@
"CMAKE_CXX_COMPILER": "clang++-14",
"CMAKE_SHARED_LINKER_FLAGS": "",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp17",
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/libc++-14-cpp17/doc/"
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp17"
}
},
{
@@ -122,8 +134,7 @@
"CMAKE_CXX_COMPILER": "clang++-14",
"CMAKE_SHARED_LINKER_FLAGS": "",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp20",
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/libc++-14-cpp20/doc/"
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp20"
}
},
{
@@ -143,6 +154,7 @@
{ "name": "g++-11", "configurePreset": "g++-11" },
{ "name": "g++-11-release", "configurePreset": "g++-11-release" },
{ "name": "clang++-13", "configurePreset": "clang++-13" },
{ "name": "clang++-14", "configurePreset": "clang++-14" },
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" },
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" },
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }
@@ -158,6 +170,7 @@
{ "name": "g++-11", "configurePreset": "g++-11", "inherits": ["test"] },
{ "name": "g++-11-release", "configurePreset": "g++-11-release", "inherits": ["test"] },
{ "name": "clang++-13", "configurePreset": "clang++-13", "inherits": ["test"] },
{ "name": "clang++-14", "configurePreset": "clang++-14", "inherits": ["test"] },
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] },
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] },
{ "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] }

View File

@@ -1,15 +1,13 @@
# boost_redis
# Boost.Redis
Boost.Redis is a high-level [Redis](https://redis.io/) client library built on top of
[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
that implements Redis plain text protocol
that implements the Redis protocol
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
It can multiplex any number of client
requests, responses, and server pushes onto a single active socket
connection to the Redis server. The requirements for using Boost.Redis are
The requirements for using Boost.Redis are:
* Boost 1.81 or greater.
* C++17 minimum.
* Boost. The library is included in Boost distributions starting with 1.84.
* C++17 or higher.
* Redis 6 or higher (must support RESP3).
* Gcc (10, 11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).
* Have basic-level knowledge about [Redis](https://redis.io/docs/)
@@ -82,8 +80,9 @@ them are
* [Client-side caching](https://redis.io/docs/manual/client-side-caching/)
The connection class supports server pushes by means of the
`boost::redis::connection::async_receive` function, the coroutine shows how
to used it
`boost::redis::connection::async_receive` function, which can be
called in the same connection that is being used to execute commands.
The coroutine below shows how to used it
```cpp
auto
@@ -92,6 +91,9 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
request req;
req.push("SUBSCRIBE", "channel");
generic_response resp;
conn->set_receive_response(resp);
// Loop while reconnection is enabled
while (conn->will_reconnect()) {
@@ -99,7 +101,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
co_await conn->async_exec(req, ignore, net::deferred);
// Loop reading Redis pushes.
for (generic_response resp;;) {
for (;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
if (ec)
@@ -108,7 +110,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
// Use the response resp in some way and then clear it.
...
resp.value().clear();
consume_one(resp);
}
}
}
@@ -674,7 +676,35 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
## Changelog
### develop (incorporates changes to conform the boost review and more)
### develop
* Deprecates the `async_receive` overload that takes a response. Users
should now first call `set_receive_response` to avoid constantly and
unnecessarily setting the same response.
* Uses `std::function` to type erase the response adapter. This change
should not influence users in any way but allowed important
simplification in the connections internals. This resulted in
massive performance improvement.
* The connection has a new member `get_usage()` that returns the
connection usage information, such as number of bytes written,
received etc.
* There are massive performance improvements in the consuming of
server pushes which are now communicated with an `asio::channel` and
therefore can be buffered which avoids blocking the socket read-loop.
Batch reads are also supported by means of `channel.try_send` and
buffered messages can be consumed synchronously with
`connection::receive`. The function `boost::redis::cancel_one` has
been added to simplify processing multiple server pushes contained
in the same `generic_response`. *IMPORTANT*: These changes may
result in more than one push in the response when
`connection::async_receive` resumes. The user must therefore be
careful when calling `resp.clear()`: either ensure that all message
have been processed or just use `consume_one`.
### v1.4.2 (incorporates changes to conform the boost review and more)
* Adds `boost::redis::config::database_index` to make it possible to
choose a database before starting running commands e.g. after an

20
benchmarks/CMakeLists.txt Normal file
View File

@@ -0,0 +1,20 @@
add_library(benchmarks_options INTERFACE)
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
add_executable(echo_server_client cpp/asio/echo_server_client.cpp)
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
add_executable(echo_server_direct cpp/asio/echo_server_direct.cpp)
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
# TODO
#=======================================================================
#.PHONY: bench
#bench:
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
# pdftoppm {input.pdf} {output.file} -png

View File

@@ -1,4 +0,0 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/Aedis.cmake")
check_required_components("@PROJECT_NAME@")

File diff suppressed because it is too large Load Diff

87
doc/Jamfile Normal file
View File

@@ -0,0 +1,87 @@
project redis/doc ;
import doxygen ;
import path ;
import sequence ;
# All paths must be absolute to work well with the Doxygen rules.
path-constant this_dir : . ;
path-constant target_dir : html ;
path-constant redis_root_dir : .. ;
path-constant include_dir : ../include ;
path-constant examples_dir : ../example ;
path-constant readme : ../README.md ;
path-constant layout_file : DoxygenLayout.xml ;
local stylesheet_files = [ path.glob $(this_dir) : *.css ] ;
local includes = [ path.glob-tree $(include_dir) : *.hpp *.cpp ] ;
local examples = [ path.glob-tree $(examples_dir) : *.hpp *.cpp ] ;
# If passed directly, several HTML_EXTRA_STYLESHEET tags are generated,
# which is not correct.
local stylesheet_arg = [ sequence.join "\"$(stylesheet_files)\"" : " " ] ;
# The doxygen rule requires the target name to end in .html to generate HTML files
doxygen doc.html
:
$(includes) $(examples) $(readme)
:
<doxygen:param>"PROJECT_NAME=Boost.Redis"
<doxygen:param>PROJECT_NUMBER="1.4.2"
<doxygen:param>PROJECT_BRIEF="A redis client library"
<doxygen:param>"STRIP_FROM_PATH=\"$(redis_root_dir)\""
<doxygen:param>"STRIP_FROM_INC_PATH=\"$(include_dir)\""
<doxygen:param>BUILTIN_STL_SUPPORT=YES
<doxygen:param>INLINE_SIMPLE_STRUCTS=YES
<doxygen:param>HIDE_UNDOC_MEMBERS=YES
<doxygen:param>HIDE_UNDOC_CLASSES=YES
<doxygen:param>SHOW_HEADERFILE=YES
<doxygen:param>SORT_BRIEF_DOCS=YES
<doxygen:param>SORT_MEMBERS_CTORS_1ST=YES
<doxygen:param>SHOW_FILES=NO
<doxygen:param>SHOW_NAMESPACES=NO
<doxygen:param>"LAYOUT_FILE=\"$(layout_file)\""
<doxygen:param>WARN_IF_INCOMPLETE_DOC=YES
<doxygen:param>FILE_PATTERNS="*.hpp *.cpp"
<doxygen:param>EXCLUDE_SYMBOLS=std
<doxygen:param>"USE_MDFILE_AS_MAINPAGE=\"$(readme)\""
<doxygen:param>SOURCE_BROWSER=YES
<doxygen:param>"HTML_EXTRA_STYLESHEET=$(stylesheet_arg)"
<doxygen:param>HTML_TIMESTAMP=YES
<doxygen:param>GENERATE_TREEVIEW=YES
<doxygen:param>FULL_SIDEBAR=NO
<doxygen:param>ENUM_VALUES_PER_LINE=0
<doxygen:param>OBFUSCATE_EMAILS=YES
<doxygen:param>USE_MATHJAX=YES
<doxygen:param>MATHJAX_VERSION=MathJax_2
<doxygen:param>MATHJAX_RELPATH="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/"
<doxygen:param>MACRO_EXPANSION=YES
<doxygen:param>HAVE_DOT=NO
<doxygen:param>CLASS_GRAPH=NO
<doxygen:param>DIRECTORY_GRAPH=NO
;
explicit doc.html ;
# The doxygen rule only informs b2 about the main HTML file, and not about
# all the doc directory that gets generated. Using the install rule copies
# only a single file, which is incorrect. This is a workaround to copy
# the generated docs to the doc/html directory, where they should be.
make copyhtml.tag : doc.html : @copy_html_dir ;
explicit copyhtml.tag ;
actions copy_html_dir
{
rm -rf $(target_dir)
mkdir -p $(target_dir)
cp -r $(<:D)/html/doc/* $(target_dir)/
echo "Stamped" > "$(<)"
}
# These are used to inform the build system of the
# means to build the integrated and stand-alone docs.
alias boostdoc ;
explicit boostdoc ;
alias boostrelease : copyhtml.tag ;
explicit boostrelease ;

49
example/CMakeLists.txt Normal file
View File

@@ -0,0 +1,49 @@
add_library(examples_main STATIC main.cpp)
target_compile_features(examples_main PRIVATE cxx_std_20)
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
macro(make_example EXAMPLE_NAME STANDARD)
add_executable(${EXAMPLE_NAME} ${EXAMPLE_NAME}.cpp)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
if (${STANDARD} STREQUAL "20")
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
endif()
endmacro()
macro(make_testable_example EXAMPLE_NAME STANDARD)
make_example(${EXAMPLE_NAME} ${STANDARD})
if (BOOST_REDIS_INTEGRATION_TESTS)
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
endif()
endmacro()
make_testable_example(cpp17_intro 17)
make_testable_example(cpp17_intro_sync 17)
make_testable_example(cpp20_intro 20)
make_testable_example(cpp20_containers 20)
make_testable_example(cpp20_json 20)
make_testable_example(cpp20_intro_tls 20)
make_example(cpp20_subscriber 20)
make_example(cpp20_streams 20)
make_example(cpp20_echo_server 20)
make_example(cpp20_resolve_with_sentinel 20)
# We test the protobuf example only on gcc.
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_package(Protobuf)
if (Protobuf_FOUND)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS person.proto)
make_testable_example(cpp20_protobuf 20)
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
endif()
endif()
if (NOT MSVC)
make_example(cpp20_chat_room 20)
endif()

View File

@@ -8,7 +8,7 @@
#include <boost/asio/detached.hpp>
#include <iostream>
namespace net = boost::asio;
namespace asio = boost::asio;
using boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
@@ -29,10 +29,10 @@ auto main(int argc, char * argv[]) -> int
response<std::string> resp;
net::io_context ioc;
asio::io_context ioc;
connection conn{ioc};
conn.async_run(cfg, {}, net::detached);
conn.async_run(cfg, {}, asio::detached);
conn.async_exec(req, resp, [&](auto ec, auto) {
if (!ec)

View File

@@ -9,7 +9,6 @@
#include <string>
#include <iostream>
namespace net = boost::asio;
using boost::redis::sync_connection;
using boost::redis::request;
using boost::redis::response;

View File

@@ -17,16 +17,23 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
namespace net = boost::asio;
using stream_descriptor = net::deferred_t::as_default_on_t<net::posix::stream_descriptor>;
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
using boost::redis::request;
using boost::redis::generic_response;
namespace asio = boost::asio;
using stream_descriptor = asio::deferred_t::as_default_on_t<asio::posix::stream_descriptor>;
using signal_set = asio::deferred_t::as_default_on_t<asio::signal_set>;
using boost::asio::async_read_until;
using boost::asio::awaitable;
using boost::asio::co_spawn;
using boost::asio::consign;
using boost::asio::deferred;
using boost::asio::detached;
using boost::asio::dynamic_buffer;
using boost::asio::redirect_error;
using boost::asio::use_awaitable;
using boost::redis::config;
using boost::redis::connection;
using boost::redis::generic_response;
using boost::redis::ignore;
using net::redirect_error;
using net::use_awaitable;
using boost::redis::request;
using boost::system::error_code;
using namespace std::chrono_literals;
@@ -34,20 +41,22 @@ using namespace std::chrono_literals;
// terminals and type messages to stdin.
auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
receiver(std::shared_ptr<connection> conn) -> awaitable<void>
{
request req;
req.push("SUBSCRIBE", "channel");
generic_response resp;
conn->set_receive_response(resp);
while (conn->will_reconnect()) {
// Subscribe to channels.
co_await conn->async_exec(req, ignore, net::deferred);
co_await conn->async_exec(req, ignore, deferred);
// Loop reading Redis push messages.
for (generic_response resp;;) {
error_code ec;
co_await conn->async_receive(resp, redirect_error(use_awaitable, ec));
for (error_code ec;;) {
co_await conn->async_receive(redirect_error(use_awaitable, ec));
if (ec)
break; // Connection lost, break so we can reconnect to channels.
std::cout
@@ -61,27 +70,27 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
}
// Publishes stdin messages to a Redis channel.
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> net::awaitable<void>
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> awaitable<void>
{
for (std::string msg;;) {
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
auto n = co_await async_read_until(*in, dynamic_buffer(msg, 1024), "\n");
request req;
req.push("PUBLISH", "channel", msg);
co_await conn->async_exec(req, ignore, net::deferred);
co_await conn->async_exec(req, ignore, deferred);
msg.erase(0, n);
}
}
// Called from the main function (see main.cpp)
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
auto stream = std::make_shared<stream_descriptor>(ex, ::dup(STDIN_FILENO));
net::co_spawn(ex, receiver(conn), net::detached);
net::co_spawn(ex, publisher(stream, conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
co_spawn(ex, receiver(conn), detached);
co_spawn(ex, publisher(stream, conn), detached);
conn->async_run(cfg, {}, consign(detached, conn));
signal_set sig_set{ex, SIGINT, SIGTERM};
co_await sig_set.async_wait();
@@ -90,7 +99,7 @@ auto co_main(config cfg) -> net::awaitable<void>
}
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
auto co_main(config const&) -> net::awaitable<void>
auto co_main(config const&) -> awaitable<void>
{
std::cout << "Requires support for posix streams." << std::endl;
co_return;

View File

@@ -14,13 +14,17 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace asio = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::ignore;
using boost::redis::config;
using boost::redis::connection;
using boost::asio::awaitable;
using boost::asio::deferred;
using boost::asio::detached;
using boost::asio::consign;
void print(std::map<std::string, std::string> const& cont)
{
@@ -35,7 +39,7 @@ void print(std::vector<int> const& cont)
}
// Stores the content of some STL containers in Redis.
auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto store(std::shared_ptr<connection> conn) -> awaitable<void>
{
std::vector<int> vec
{1, 2, 3, 4, 5, 6};
@@ -47,10 +51,10 @@ auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
req.push_range("RPUSH", "rpush-key", vec);
req.push_range("HSET", "hset-key", map);
co_await conn->async_exec(req, ignore, net::deferred);
co_await conn->async_exec(req, ignore, deferred);
}
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto hgetall(std::shared_ptr<connection> conn) -> awaitable<void>
{
// A request contains multiple commands.
request req;
@@ -60,13 +64,13 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
response<std::map<std::string, std::string>> resp;
// Executes the request and reads the response.
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, deferred);
print(std::get<0>(resp).value());
}
// Retrieves in a transaction.
auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
{
request req;
req.push("MULTI");
@@ -81,17 +85,17 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
response<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
> resp;
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, deferred);
print(std::get<0>(std::get<3>(resp).value()).value().value());
print(std::get<1>(std::get<3>(resp).value()).value().value());
}
// Called from the main function (see main.cpp)
net::awaitable<void> co_main(config cfg)
awaitable<void> co_main(config cfg)
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
conn->async_run(cfg, {}, consign(detached, conn));
co_await store(conn);
co_await transaction(conn);

View File

@@ -14,10 +14,10 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
using tcp_socket = net::deferred_t::as_default_on_t<net::ip::tcp::socket>;
using tcp_acceptor = net::deferred_t::as_default_on_t<net::ip::tcp::acceptor>;
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
namespace asio = boost::asio;
using tcp_socket = asio::deferred_t::as_default_on_t<asio::ip::tcp::socket>;
using tcp_acceptor = asio::deferred_t::as_default_on_t<asio::ip::tcp::acceptor>;
using signal_set = asio::deferred_t::as_default_on_t<asio::signal_set>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
@@ -25,16 +25,16 @@ using boost::system::error_code;
using boost::redis::connection;
using namespace std::chrono_literals;
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> asio::awaitable<void>
{
request req;
response<std::string> resp;
for (std::string buffer;;) {
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
auto n = co_await asio::async_read_until(socket, asio::dynamic_buffer(buffer, 1024), "\n");
req.push("PING", buffer);
co_await conn->async_exec(req, resp, net::deferred);
co_await net::async_write(socket, net::buffer(std::get<0>(resp).value()));
co_await conn->async_exec(req, resp, asio::deferred);
co_await asio::async_write(socket, asio::buffer(std::get<0>(resp).value()));
std::get<0>(resp).value().clear();
req.clear();
buffer.erase(0, n);
@@ -42,25 +42,25 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
}
// Listens for tcp connections.
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto listener(std::shared_ptr<connection> conn) -> asio::awaitable<void>
{
try {
auto ex = co_await net::this_coro::executor;
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
auto ex = co_await asio::this_coro::executor;
tcp_acceptor acc(ex, {asio::ip::tcp::v4(), 55555});
for (;;)
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
asio::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), asio::detached);
} catch (std::exception const& e) {
std::clog << "Listener: " << e.what() << std::endl;
}
}
// Called from the main function (see main.cpp)
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, listener(conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
asio::co_spawn(ex, listener(conn), asio::detached);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();

View File

@@ -13,17 +13,17 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace asio = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
using boost::redis::connection;
// Called from the main function (see main.cpp)
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
// A request containing only a ping command.
request req;
@@ -33,7 +33,7 @@ auto co_main(config cfg) -> net::awaitable<void>
response<std::string> resp;
// Executes the request.
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, asio::deferred);
conn->cancel();
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;

View File

@@ -13,20 +13,20 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace asio = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::connection;
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
auto verify_certificate(bool, asio::ssl::verify_context&) -> bool
{
std::cout << "set_verify_callback" << std::endl;
return true;
}
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
cfg.use_ssl = true;
cfg.username = "aedis";
@@ -34,18 +34,18 @@ auto co_main(config cfg) -> net::awaitable<void>
cfg.addr.host = "db.occase.de";
cfg.addr.port = "6380";
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
request req;
req.push("PING");
response<std::string> resp;
conn->next_layer().set_verify_mode(net::ssl::verify_peer);
conn->next_layer().set_verify_mode(asio::ssl::verify_peer);
conn->next_layer().set_verify_callback(verify_certificate);
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, asio::deferred);
conn->cancel();
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;

View File

@@ -23,7 +23,7 @@
#include <boost/redis/resp3/serialization.hpp>
#include <boost/json/src.hpp>
namespace net = boost::asio;
namespace asio = boost::asio;
using namespace boost::describe;
using boost::redis::request;
using boost::redis::response;
@@ -41,18 +41,18 @@ struct user {
// The type must be described for serialization to work.
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
// Boost.Redis customization points (examples/json.hpp)
// Boost.Redis customization points (example/json.hpp)
void boost_redis_to_bulk(std::string& to, user const& u)
{ boost::redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); }
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code&)
{ u = boost::json::value_to<user>(boost::json::parse(sv)); }
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
// user object that will be stored in Redis in json format.
user const u{"Joao", "58", "Brazil"};
@@ -64,7 +64,7 @@ auto co_main(config cfg) -> net::awaitable<void>
response<ignore_t, user> resp;
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, asio::deferred);
conn->cancel();
// Prints the first ping

View File

@@ -19,7 +19,7 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace asio = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::operation;
@@ -27,10 +27,10 @@ using boost::redis::ignore_t;
using boost::redis::config;
using boost::redis::connection;
// The protobuf type described in examples/person.proto
// The protobuf type described in example/person.proto
using tutorial::person;
// Boost.Redis customization points (examples/protobuf.hpp)
// Boost.Redis customization points (example/protobuf.hpp)
namespace tutorial
{
@@ -58,11 +58,11 @@ void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_
using tutorial::boost_redis_to_bulk;
using tutorial::boost_redis_from_bulk;
net::awaitable<void> co_main(config cfg)
asio::awaitable<void> co_main(config cfg)
{
auto ex = co_await net::this_coro::executor;
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
person p;
p.set_name("Louis");
@@ -76,7 +76,7 @@ net::awaitable<void> co_main(config cfg)
response<ignore_t, person> resp;
// Sends the request and receives the response.
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, asio::deferred);
conn->cancel();
std::cout

View File

@@ -12,8 +12,8 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
using endpoints = net::ip::tcp::resolver::results_type;
namespace asio = boost::asio;
using endpoints = asio::ip::tcp::resolver::results_type;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
@@ -22,18 +22,18 @@ using boost::redis::address;
using boost::redis::connection;
auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
{ return asio::redirect_error(asio::use_awaitable, ec); }
// For more info see
// - https://redis.io/docs/manual/sentinel.
// - https://redis.io/docs/reference/sentinel-clients.
auto resolve_master_address(std::vector<address> const& addresses) -> net::awaitable<address>
auto resolve_master_address(std::vector<address> const& addresses) -> asio::awaitable<address>
{
request req;
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
req.push("QUIT");
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
response<std::optional<std::array<std::string, 2>>, ignore_t> resp;
for (auto addr : addresses) {
@@ -43,7 +43,7 @@ auto resolve_master_address(std::vector<address> const& addresses) -> net::await
// TODO: async_run and async_exec should be lauched in
// parallel here so we can wait for async_run completion
// before eventually calling it again.
conn->async_run(cfg, {}, net::consign(net::detached, conn));
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
co_await conn->async_exec(req, resp, redir(ec));
conn->cancel();
conn->reset_stream();
@@ -54,7 +54,7 @@ auto resolve_master_address(std::vector<address> const& addresses) -> net::await
co_return address{};
}
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
// A list of sentinel addresses from which only one is responsive.
// This simulates sentinels that are down.

View File

@@ -18,16 +18,18 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::redis::request;
using boost::redis::generic_response;
using boost::redis::consume_one;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::ignore;
using boost::redis::error;
using boost::system::error_code;
using boost::redis::connection;
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
using signal_set = asio::deferred_t::as_default_on_t<asio::signal_set>;
/* This example will subscribe and read pushes indefinitely.
*
@@ -47,39 +49,49 @@ using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
// Receives server pushes.
auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
{
request req;
req.push("SUBSCRIBE", "channel");
generic_response resp;
conn->set_receive_response(resp);
// Loop while reconnection is enabled
while (conn->will_reconnect()) {
// Reconnect to channels.
co_await conn->async_exec(req, ignore, net::deferred);
// Reconnect to the channels.
co_await conn->async_exec(req, ignore, asio::deferred);
// Loop reading Redis pushs messages.
for (generic_response resp;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
for (error_code ec;;) {
// First tries to read any buffered pushes.
conn->receive(ec);
if (ec == error::sync_receive_push_failed) {
ec = {};
co_await conn->async_receive(asio::redirect_error(asio::use_awaitable, ec));
}
if (ec)
break; // Connection lost, break so we can reconnect to channels.
std::cout
<< resp.value().at(1).value
<< " " << resp.value().at(2).value
<< " " << resp.value().at(3).value
<< std::endl;
resp.value().clear();
consume_one(resp);
}
}
}
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, receiver(conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
asio::co_spawn(ex, receiver(conn), asio::detached);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();

View File

@@ -11,13 +11,13 @@
#include <boost/asio/io_context.hpp>
#include <iostream>
namespace net = boost::asio;
namespace asio = boost::asio;
using boost::redis::config;
using boost::redis::logger;
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
extern net::awaitable<void> co_main(config);
extern asio::awaitable<void> co_main(config);
auto main(int argc, char * argv[]) -> int
{
@@ -29,8 +29,8 @@ auto main(int argc, char * argv[]) -> int
cfg.addr.port = argv[2];
}
net::io_context ioc;
net::co_spawn(ioc, std::move(co_main(cfg)), [](std::exception_ptr p) {
asio::io_context ioc;
asio::co_spawn(ioc, co_main(cfg), [](std::exception_ptr p) {
if (p)
std::rethrow_exception(p);
});

View File

@@ -1,5 +1,5 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -92,7 +92,8 @@ private:
public:
explicit general_aggregate(Result* c = nullptr): result_(c) {}
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code&)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
@@ -114,7 +115,8 @@ private:
public:
explicit general_simple(Node* t = nullptr) : result_(t) {}
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code&)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
@@ -136,11 +138,8 @@ class simple_impl {
public:
void on_value_available(Result&) {}
void
operator()(
Result& result,
resp3::basic_node<std::string_view> const& n,
system::error_code& ec)
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& n, system::error_code& ec)
{
if (is_aggregate(n.data_type)) {
ec = redis::error::expects_resp3_simple_type;
@@ -160,11 +159,8 @@ public:
void on_value_available(Result& result)
{ hint_ = std::end(result); }
void
operator()(
Result& result,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (is_aggregate(nd.data_type)) {
if (nd.data_type != resp3::type::set)
@@ -195,11 +191,8 @@ public:
void on_value_available(Result& result)
{ current_ = std::end(result); }
void
operator()(
Result& result,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (is_aggregate(nd.data_type)) {
if (element_multiplicity(nd.data_type) != 2)
@@ -233,11 +226,8 @@ class vector_impl {
public:
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (is_aggregate(nd.data_type)) {
auto const m = element_multiplicity(nd.data_type);
@@ -257,11 +247,8 @@ private:
public:
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (is_aggregate(nd.data_type)) {
if (i_ != -1) {
@@ -292,11 +279,8 @@ struct list_impl {
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (!is_aggregate(nd.data_type)) {
BOOST_ASSERT(nd.aggregate_size == 1);
@@ -365,7 +349,8 @@ private:
response_type* result_;
typename impl_map<Result>::type impl_;
bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
template <class String>
bool set_if_resp3_error(resp3::basic_node<String> const& nd) noexcept
{
switch (nd.data_type) {
case resp3::type::null:
@@ -387,10 +372,8 @@ public:
}
}
void
operator()(
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
@@ -414,7 +397,8 @@ private:
response_type* result_;
typename impl_map<T>::type impl_{};
bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
template <class String>
bool set_if_resp3_error(resp3::basic_node<String> const& nd) noexcept
{
switch (nd.data_type) {
case resp3::type::blob_error:
@@ -429,9 +413,10 @@ private:
public:
explicit wrapper(response_type* o = nullptr) : result_(o) {}
template <class String>
void
operator()(
resp3::basic_node<std::string_view> const& nd,
resp3::basic_node<String> const& nd,
system::error_code& ec)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -23,8 +23,8 @@ namespace boost::redis::adapter::detail
class ignore_adapter {
public:
void
operator()(std::size_t, resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
template <class String>
void operator()(std::size_t, resp3::basic_node<String> const& nd, system::error_code& ec)
{
switch (nd.data_type) {
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
@@ -59,11 +59,8 @@ public:
auto get_supported_response_size() const noexcept
{ return size;}
void
operator()(
std::size_t i,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(std::size_t i, resp3::basic_node<String> const& nd, system::error_code& ec)
{
using std::visit;
// I am usure whether this should be an error or an assertion.
@@ -88,11 +85,8 @@ public:
get_supported_response_size() const noexcept
{ return static_cast<std::size_t>(-1);}
void
operator()(
std::size_t,
resp3::basic_node<std::string_view> const& nd,
system::error_code& ec)
template <class String>
void operator()(std::size_t, resp3::basic_node<String> const& nd, system::error_code& ec)
{
adapter_(nd, ec);
}
@@ -142,7 +136,8 @@ class wrapper {
public:
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
{ return adapter_(0, nd, ec); }
[[nodiscard]]

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -116,7 +116,8 @@ public:
}
}
void count(resp3::basic_node<std::string_view> const& nd)
template <class String>
void count(resp3::basic_node<String> const& nd)
{
if (nd.depth == 1) {
if (is_aggregate(nd.data_type))
@@ -131,7 +132,8 @@ public:
++i_;
}
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
{
using std::visit;

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,5 +1,5 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -171,7 +171,6 @@ public:
* To cancel an ongoing receive operation apps should call
* `connection::cancel(operation::receive)`.
*
* @param response Response object.
* @param token Completion token.
*
* For an example see cpp20_subscriber.cpp. The completion token must
@@ -184,10 +183,32 @@ public:
* Where the second parameter is the size of the push received in
* bytes.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_receive(CompletionToken token = CompletionToken{})
{ return impl_.async_receive(std::move(token)); }
/** @brief Receives server pushes synchronously without blocking.
*
* Receives a server push synchronously by calling `try_receive` on
* the underlying channel. If the operation fails because
* `try_receive` returns `false`, `ec` will be set to
* `boost::redis::error::sync_receive_push_failed`.
*
* @param ec Contains the error if any occurred.
*
* @returns The number of bytes read from the socket.
*/
std::size_t receive(system::error_code& ec)
{
return impl_.receive(ec);
}
template <
class Response = ignore_t,
class CompletionToken = asio::default_completion_token_t<executor_type>
>
[[deprecated("Set the response with set_receive_response and use the other overload.")]]
auto
async_receive(
Response& response,
@@ -242,7 +263,6 @@ public:
* @li operation::all: Cancels all operations listed above.
*
* @param op: The operation to be cancelled.
* @returns The number of operations that have been canceled.
*/
void cancel(operation op = operation::all)
{
@@ -282,6 +302,15 @@ public:
auto const& next_layer() const noexcept
{ return impl_.next_layer(); }
/// Sets the response object of `async_receive` operations.
template <class Response>
void set_receive_response(Response& response)
{ impl_.set_receive_response(response); }
/// Returns connection usage information.
usage get_usage() const noexcept
{ return impl_.get_usage(); }
private:
using timer_type =
asio::basic_waitable_timer<
@@ -342,11 +371,23 @@ public:
/// Calls `boost::redis::basic_connection::async_receive`.
template <class Response, class CompletionToken>
[[deprecated("Set the response with set_receive_response and use the other overload.")]]
auto async_receive(Response& response, CompletionToken token)
{
return impl_.async_receive(response, std::move(token));
}
/// Calls `boost::redis::basic_connection::async_receive`.
template <class CompletionToken>
auto async_receive(CompletionToken token)
{ return impl_.async_receive(std::move(token)); }
/// Calls `boost::redis::basic_connection::receive`.
std::size_t receive(system::error_code& ec)
{
return impl_.receive(ec);
}
/// Calls `boost::redis::basic_connection::async_exec`.
template <class Response, class CompletionToken>
auto async_exec(request const& req, Response& resp, CompletionToken token)
@@ -373,6 +414,15 @@ public:
void reset_stream()
{ impl_.reset_stream();}
/// Sets the response object of `async_receive` operations.
template <class Response>
void set_receive_response(Response& response)
{ impl_.set_receive_response(response); }
/// Returns connection usage information.
usage get_usage() const noexcept
{ return impl_.get_usage(); }
private:
void
async_run_impl(

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -24,11 +24,12 @@
namespace boost::redis::detail {
template <class HealthChecker, class Connection>
template <class HealthChecker, class Connection, class Logger>
class ping_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
@@ -37,28 +38,39 @@ public:
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
if (checker_->checker_has_exited_) {
logger_.trace("ping_op: checker has exited. Exiting ...");
self.complete({});
return;
}
BOOST_ASIO_CORO_YIELD
conn_->async_exec(checker_->req_, checker_->resp_, std::move(self));
BOOST_REDIS_CHECK_OP0(checker_->wait_timer_.cancel();)
if (ec || is_cancelled(self)) {
logger_.trace("ping_op: error/cancelled (1).");
checker_->wait_timer_.cancel();
self.complete(!!ec ? ec : asio::error::operation_aborted);
return;
}
// Wait before pinging again.
checker_->ping_timer_.expires_after(checker_->ping_interval_);
BOOST_ASIO_CORO_YIELD
checker_->ping_timer_.async_wait(std::move(self));
BOOST_REDIS_CHECK_OP0(;)
if (ec || is_cancelled(self)) {
logger_.trace("ping_op: error/cancelled (2).");
self.complete(!!ec ? ec : asio::error::operation_aborted);
return;
}
}
}
};
template <class HealthChecker, class Connection>
template <class HealthChecker, class Connection, class Logger>
class check_timeout_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
@@ -69,14 +81,20 @@ public:
checker_->wait_timer_.expires_after(2 * checker_->ping_interval_);
BOOST_ASIO_CORO_YIELD
checker_->wait_timer_.async_wait(std::move(self));
BOOST_REDIS_CHECK_OP0(;)
if (ec || is_cancelled(self)) {
logger_.trace("check-timeout-op: error/canceled. Exiting ...");
self.complete(!!ec ? ec : asio::error::operation_aborted);
return;
}
if (checker_->resp_.has_error()) {
logger_.trace("check-timeout-op: Response error. Exiting ...");
self.complete({});
return;
}
if (checker_->resp_.value().empty()) {
logger_.trace("check-timeout-op: Response has no value. Exiting ...");
checker_->ping_timer_.cancel();
conn_->cancel(operation::run);
checker_->checker_has_exited_ = true;
@@ -91,11 +109,12 @@ public:
}
};
template <class HealthChecker, class Connection>
template <class HealthChecker, class Connection, class Logger>
class check_health_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
@@ -109,6 +128,7 @@ public:
BOOST_ASIO_CORO_REENTER (coro_)
{
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
logger_.trace("check-health-op: timeout disabled.");
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
self.complete({});
@@ -117,13 +137,16 @@ public:
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return checker_->async_ping(*conn_, token); },
[this](auto token) { return checker_->async_check_timeout(*conn_, token);}
[this](auto token) { return checker_->async_ping(*conn_, logger_, token); },
[this](auto token) { return checker_->async_check_timeout(*conn_, logger_, token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
logger_.on_check_health(ec1, ec2);
if (is_cancelled(self)) {
logger_.trace("check-health-op: canceled. Exiting ...");
self.complete(asio::error::operation_aborted);
return;
}
@@ -163,15 +186,20 @@ public:
template <
class Connection,
class Logger,
class CompletionToken = asio::default_completion_token_t<Executor>
>
auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{})
auto
async_check_health(
Connection& conn,
Logger l,
CompletionToken token = CompletionToken{})
{
checker_has_exited_ = false;
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(check_health_op<health_checker, Connection>{this, &conn}, token, conn);
>(check_health_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn);
}
std::size_t cancel(operation op)
@@ -189,27 +217,27 @@ public:
}
private:
template <class Connection, class CompletionToken>
auto async_ping(Connection& conn, CompletionToken token)
template <class Connection, class Logger, class CompletionToken>
auto async_ping(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(ping_op<health_checker, Connection>{this, &conn}, token, conn, ping_timer_);
>(ping_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn, ping_timer_);
}
template <class Connection, class CompletionToken>
auto async_check_timeout(Connection& conn, CompletionToken token)
template <class Connection, class Logger, class CompletionToken>
auto async_check_timeout(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(check_timeout_op<health_checker, Connection>{this, &conn}, token, conn, wait_timer_);
>(check_timeout_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn, wait_timer_);
}
template <class, class> friend class ping_op;
template <class, class> friend class check_timeout_op;
template <class, class> friend class check_health_op;
template <class, class, class> friend class ping_op;
template <class, class, class> friend class check_timeout_op;
template <class, class, class> friend class check_health_op;
timer_type ping_timer_;
timer_type wait_timer_;

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,291 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_READ_HPP
#define BOOST_REDIS_READ_HPP
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/resp3/parser.hpp>
#include <boost/redis/adapter/ignore.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
#include <string_view>
#include <limits>
namespace boost::redis::detail {
template <class DynamicBuffer>
std::string_view buffer_view(DynamicBuffer buf) noexcept
{
char const* start = static_cast<char const*>(buf.data(0, buf.size()).data());
return std::string_view{start, std::size(buf)};
}
template <class AsyncReadStream, class DynamicBuffer>
class append_some_op {
private:
AsyncReadStream& stream_;
DynamicBuffer buf_;
std::size_t size_ = 0;
std::size_t tmp_ = 0;
asio::coroutine coro_{};
public:
append_some_op(AsyncReadStream& stream, DynamicBuffer buf, std::size_t size)
: stream_ {stream}
, buf_ {std::move(buf)}
, size_{size}
{ }
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro_)
{
tmp_ = buf_.size();
buf_.grow(size_);
BOOST_ASIO_CORO_YIELD
stream_.async_read_some(buf_.data(tmp_, size_), std::move(self));
if (ec) {
self.complete(ec, 0);
return;
}
buf_.shrink(buf_.size() - tmp_ - n);
self.complete({}, n);
}
}
};
template <class AsyncReadStream, class DynamicBuffer, class CompletionToken>
auto
async_append_some(
AsyncReadStream& stream,
DynamicBuffer buffer,
std::size_t size,
CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code, std::size_t)
>(append_some_op<AsyncReadStream, DynamicBuffer> {stream, buffer, size}, token, stream);
}
template <
class AsyncReadStream,
class DynamicBuffer,
class ResponseAdapter>
class parse_op {
private:
AsyncReadStream& stream_;
DynamicBuffer buf_;
resp3::parser parser_;
ResponseAdapter adapter_;
bool needs_rescheduling_ = true;
system::error_code ec_;
asio::coroutine coro_{};
static std::size_t const growth = 1024;
public:
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
: stream_ {stream}
, buf_ {std::move(buf)}
, adapter_ {std::move(adapter)}
{ }
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_)
{
while (!resp3::parse(parser_, buffer_view(buf_), adapter_, ec)) {
needs_rescheduling_ = false;
BOOST_ASIO_CORO_YIELD
async_append_some(
stream_, buf_, parser_.get_suggested_buffer_growth(growth),
std::move(self));
if (ec) {
self.complete(ec, 0);
return;
}
}
ec_ = ec;
if (needs_rescheduling_) {
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
}
self.complete(ec_, parser_.get_consumed());
}
}
};
/** \brief Reads a complete response to a command sychronously.
*
* This function reads a complete response to a command or a
* server push synchronously. For example
*
* @code
* int resp;
* std::string buffer;
* resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
* @endcode
*
* For a complete example see examples/intro_sync.cpp. This function
* is implemented in terms of one or more calls to @c
* asio::read_until and @c asio::read functions, and is known as a @a
* composed @a operation. Furthermore, the implementation may read
* additional bytes from the stream that lie past the end of the
* message being read. These additional bytes are stored in the
* dynamic buffer, which must be preserved for subsequent reads.
*
* \param stream The stream from which to read e.g. a tcp socket.
* \param buf Dynamic buffer (version 2).
* \param adapter The response adapter.
* \param ec If an error occurs, it will be assigned to this paramter.
* \returns The number of bytes that have been consumed from the dynamic buffer.
*
* \remark This function calls buf.consume() in each chunk of data
* after it has been passed to the adapter. Users must not consume
* the bytes after it returns.
*/
template <
class SyncReadStream,
class DynamicBuffer,
class ResponseAdapter
>
auto
read(
SyncReadStream& stream,
DynamicBuffer buf,
ResponseAdapter adapter,
system::error_code& ec) -> std::size_t
{
static std::size_t const growth = 1024;
resp3::parser parser;
while (!parser.done()) {
auto const res = parser.consume(detail::buffer_view(buf), ec);
if (ec)
return 0UL;
if (!res.has_value()) {
auto const size_before = buf.size();
buf.grow(parser.get_suggested_buffer_growth(growth));
auto const n =
stream.read_some(
buf.data(size_before, parser.get_suggested_buffer_growth(growth)),
ec);
if (ec)
return 0UL;
buf.shrink(buf.size() - size_before - n);
continue;
}
adapter(res.value(), ec);
if (ec)
return 0UL;
}
return parser.get_consumed();
}
/** \brief Reads a complete response to a command sychronously.
*
* Same as the error_code overload but throws on error.
*/
template<
class SyncReadStream,
class DynamicBuffer,
class ResponseAdapter = adapter::ignore>
auto
read(
SyncReadStream& stream,
DynamicBuffer buf,
ResponseAdapter adapter = ResponseAdapter{})
{
system::error_code ec;
auto const n = redis::detail::read(stream, buf, adapter, ec);
if (ec)
BOOST_THROW_EXCEPTION(system::system_error{ec});
return n;
}
/** \brief Reads a complete response to a Redis command asynchronously.
*
* This function reads a complete response to a command or a
* server push asynchronously. For example
*
* @code
* std::string buffer;
* std::set<std::string> resp;
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
* @endcode
*
* For a complete example see examples/transaction.cpp. This function
* is implemented in terms of one or more calls to @c
* asio::async_read_until and @c asio::async_read functions, and is
* known as a @a composed @a operation. Furthermore, the
* implementation may read additional bytes from the stream that lie
* past the end of the message being read. These additional bytes are
* stored in the dynamic buffer, which must be preserved for
* subsequent reads.
*
* \param stream The stream from which to read e.g. a tcp socket.
* \param buffer Dynamic buffer (version 2).
* \param adapter The response adapter.
* \param token The completion token.
*
* The completion handler will receive as a parameter the total
* number of bytes transferred from the stream and must have the
* following signature
*
* @code
* void(system::error_code, std::size_t);
* @endcode
*
* \remark This function calls buf.consume() in each chunk of data
* after it has been passed to the adapter. Users must not consume
* the bytes after it returns.
*/
template <
class AsyncReadStream,
class DynamicBuffer,
class ResponseAdapter = adapter::ignore,
class CompletionToken = asio::default_completion_token_t<typename AsyncReadStream::executor_type>
>
auto async_read(
AsyncReadStream& stream,
DynamicBuffer buffer,
ResponseAdapter adapter = ResponseAdapter{},
CompletionToken&& token =
asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
{
return asio::async_compose
< CompletionToken
, void(system::error_code, std::size_t)
>(parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
token,
stream);
}
} // boost::redis::detail
#endif // BOOST_REDIS_READ_HPP

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -50,8 +50,15 @@ struct hello_op {
BOOST_ASIO_CORO_YIELD
conn_->async_exec(runner_->hello_req_, runner_->hello_resp_, std::move(self));
logger_.on_hello(ec, runner_->hello_resp_);
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
self.complete(ec);
if (ec || runner_->has_error_in_response() || is_cancelled(self)) {
logger_.trace("hello-op: error/canceled. Exiting ...");
conn_->cancel(operation::run);
self.complete(!!ec ? ec : asio::error::operation_aborted);
return;
}
self.complete({});
}
}
};
@@ -84,12 +91,14 @@ public:
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return runner_->async_run_all(*conn_, logger_, token); },
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, token); },
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, logger_, token); },
[this](auto token) { return runner_->async_hello(*conn_, logger_, token); }
).async_wait(
asio::experimental::wait_for_all(),
std::move(self));
logger_.on_runner(ec0, ec1, ec2);
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
@@ -232,10 +241,27 @@ private:
else
hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname);
if (cfg_.database_index)
if (cfg_.database_index && cfg_.database_index.value() != 0)
hello_req_.push("SELECT", cfg_.database_index.value());
}
bool has_error_in_response() const noexcept
{
if (!hello_resp_.has_value())
return true;
auto f = [](auto const& e)
{
switch (e.data_type) {
case resp3::type::simple_error:
case resp3::type::blob_error: return true;
default: return false;
}
};
return std::any_of(std::cbegin(hello_resp_.value()), std::cend(hello_resp_.value()), f);
}
resolver_type resv_;
connector_type ctor_;
handshaker_type hsher_;

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -75,6 +75,12 @@ enum class error
/// SSL handshake timeout
ssl_handshake_timeout,
/// Can't receive push synchronously without blocking
sync_receive_push_failed,
/// Incompatible node depth.
incompatible_node_depth,
};
/** \internal

View File

@@ -1,5 +1,5 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -41,6 +41,9 @@ struct error_category_impl : system::error_category {
case error::resolve_timeout: return "Resolve timeout.";
case error::connect_timeout: return "Connect timeout.";
case error::pong_timeout: return "Pong timeout.";
case error::ssl_handshake_timeout: return "SSL handshake timeout.";
case error::sync_receive_push_failed: return "Can't receive server push synchronously without blocking.";
case error::incompatible_node_depth: return "Incompatible node depth.";
default: BOOST_ASSERT(false); return "Boost.Redis error.";
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -25,7 +25,7 @@ void logger::on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::r
write_prefix();
std::clog << "Resolve results: ";
std::clog << "run-all-op: resolve addresses ";
if (ec) {
std::clog << ec.message() << std::endl;
@@ -51,7 +51,7 @@ void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint co
write_prefix();
std::clog << "Connected to endpoint: ";
std::clog << "run-all-op: connected to endpoint ";
if (ec)
std::clog << ec.message() << std::endl;
@@ -68,7 +68,7 @@ void logger::on_ssl_handshake(system::error_code const& ec)
write_prefix();
std::clog << "SSL handshake: " << ec.message() << std::endl;
std::clog << "Runner: SSL handshake " << ec.message() << std::endl;
}
void logger::on_connection_lost(system::error_code const& ec)
@@ -97,9 +97,38 @@ logger::on_write(
write_prefix();
if (ec)
std::clog << "Write: " << ec.message();
std::clog << "writer-op: " << ec.message();
else
std::clog << "Bytes written: " << std::size(payload);
std::clog << "writer-op: " << std::size(payload) << " bytes written.";
std::clog << std::endl;
}
void logger::on_read(system::error_code const& ec, std::size_t n)
{
if (level_ < level::info)
return;
write_prefix();
if (ec)
std::clog << "reader-op: " << ec.message();
else
std::clog << "reader-op: " << n << " bytes read.";
std::clog << std::endl;
}
void logger::on_run(system::error_code const& reader_ec, system::error_code const& writer_ec)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "run-op: "
<< reader_ec.message() << " (reader), "
<< writer_ec.message() << " (writer)";
std::clog << std::endl;
}
@@ -115,14 +144,60 @@ logger::on_hello(
write_prefix();
if (ec) {
std::clog << "Hello: " << ec.message();
std::clog << "hello-op: " << ec.message();
if (resp.has_error())
std::clog << " (" << resp.error().diagnostic << ")";
} else {
std::clog << "Hello: Success";
std::clog << "hello-op: Success";
}
std::clog << std::endl;
}
void
logger::on_runner(
system::error_code const& run_all_ec,
system::error_code const& health_check_ec,
system::error_code const& hello_ec)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "runner-op: "
<< run_all_ec.message() << " (async_run_all), "
<< health_check_ec.message() << " (async_health_check) "
<< hello_ec.message() << " (async_hello).";
std::clog << std::endl;
}
void
logger::on_check_health(
system::error_code const& ping_ec,
system::error_code const& timeout_ec)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "check-health-op: "
<< ping_ec.message() << " (async_ping), "
<< timeout_ec.message() << " (async_check_timeout).";
std::clog << std::endl;
}
void logger::trace(std::string_view reason)
{
if (level_ < level::debug)
return;
write_prefix();
std::clog << reason << std::endl;
}
} // boost::redis

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -0,0 +1,48 @@
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/response.hpp>
#include <boost/redis/error.hpp>
#include <boost/assert.hpp>
namespace boost::redis
{
void consume_one(generic_response& r, system::error_code& ec)
{
if (r.has_error())
return; // Nothing to consume.
if (std::empty(r.value()))
return; // Nothing to consume.
auto const depth = r.value().front().depth;
// To simplify we will refuse to consume any data-type that is not
// a root node. I think there is no use for that and it is complex
// since it requires updating parent nodes.
if (depth != 0) {
ec = error::incompatible_node_depth;
return;
}
auto f = [depth](auto const& e)
{ return e.depth == depth; };
auto match = std::find_if(std::next(std::cbegin(r.value())), std::cend(r.value()), f);
r.value().erase(std::cbegin(r.value()), match);
}
void consume_one(generic_response& r)
{
system::error_code ec;
consume_one(r, ec);
if (ec)
throw system::system_error(ec);
}
} // boost::redis::resp3

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -19,6 +19,10 @@ namespace boost::redis {
* @ingroup high-level-api
*
* The class can be passed to the connection objects to log to `std::clog`
*
* Notice that currently this class has no stable interface. Users
* that don't want any logging can disable it by contructing a logger
* with logger::level::emerg to the connection.
*/
class logger {
public:
@@ -26,7 +30,11 @@ public:
* @ingroup high-level-api
*/
enum class level
{ /// Emergency
{
/// Disabled
disabled,
/// Emergency
emerg,
/// Alert
@@ -56,7 +64,7 @@ public:
*
* @param l Log level.
*/
logger(level l = level::info)
logger(level l = level::disabled)
: level_{l}
{}
@@ -98,6 +106,22 @@ public:
*/
void on_write(system::error_code const& ec, std::string const& payload);
/** @brief Called when the read operation completes.
* @ingroup high-level-api
*
* @param ec Error code returned by the read operation.
* @param n Number of bytes read.
*/
void on_read(system::error_code const& ec, std::size_t n);
/** @brief Called when the run operation completes.
* @ingroup high-level-api
*
* @param reader_ec Error code returned by the read operation.
* @param writer_ec Error code returned by the write operation.
*/
void on_run(system::error_code const& reader_ec, system::error_code const& writer_ec);
/** @brief Called when the `HELLO` request completes.
* @ingroup high-level-api
*
@@ -116,6 +140,26 @@ public:
prefix_ = prefix;
}
/** @brief Called when the runner operation completes.
* @ingroup high-level-api
*
* @param run_all_ec Error code returned by the run_all operation.
* @param health_check_ec Error code returned by the health checker operation.
* @param hello_ec Error code returned by the health checker operation.
*/
void
on_runner(
system::error_code const& run_all_ec,
system::error_code const& health_check_ec,
system::error_code const& hello_ec);
void
on_check_health(
system::error_code const& ping_ec,
system::error_code const& check_timeout_ec);
void trace(std::string_view reason);
private:
void write_prefix();
level level_;

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -84,8 +84,12 @@ public:
request(config cfg = config{true, false, true, true})
: cfg_{cfg} {}
//// Returns the number of responses expected for this request.
[[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
{ return expected_responses_;};
//// Returns the number of commands contained in this request.
[[nodiscard]] auto size() const noexcept -> std::size_t
[[nodiscard]] auto get_commands() const noexcept -> std::size_t
{ return commands_;};
[[nodiscard]] auto payload() const noexcept -> std::string_view
@@ -99,6 +103,7 @@ public:
{
payload_.clear();
commands_ = 0;
expected_responses_ = 0;
has_hello_priority_ = false;
}
@@ -303,8 +308,10 @@ public:
private:
void check_cmd(std::string_view cmd)
{
++commands_;
if (!detail::has_response(cmd))
++commands_;
++expected_responses_;
if (cmd == "HELLO")
has_hello_priority_ = cfg_.hello_with_priority;
@@ -313,6 +320,7 @@ private:
config cfg_;
std::string payload_;
std::size_t commands_ = 0;
std::size_t expected_responses_ = 0;
bool has_hello_priority_ = false;
};

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -9,6 +9,7 @@
#include <boost/assert.hpp>
#include <charconv>
#include <limits>
namespace boost::redis::resp3 {
@@ -21,6 +22,16 @@ void to_int(int_type& i, std::string_view sv, system::error_code& ec)
parser::parser()
{
reset();
}
void parser::reset()
{
depth_ = 0;
sizes_ = {{1}};
bulk_length_ = (std::numeric_limits<unsigned long>::max)();
bulk_ = type::invalid;
consumed_ = 0;
sizes_[0] = 2; // The sentinel must be more than 1.
}

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -10,7 +10,6 @@
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <limits>
#include <string_view>
#include <cstdint>
#include <optional>
@@ -31,22 +30,22 @@ private:
// The current depth. Simple data types will have depth 0, whereas
// the elements of aggregates will have depth 1. Embedded types
// will have increasing depth.
std::size_t depth_ = 0;
std::size_t depth_;
// The parser supports up to 5 levels of nested structures. The
// first element in the sizes stack is a sentinel and must be
// different from 1.
std::array<std::size_t, max_embedded_depth + 1> sizes_ = {{1}};
std::array<std::size_t, max_embedded_depth + 1> sizes_;
// Contains the length expected in the next bulk read.
int_type bulk_length_ = (std::numeric_limits<unsigned long>::max)();
int_type bulk_length_;
// The type of the next bulk. Contains type::invalid if no bulk is
// expected.
type bulk_ = type::invalid;
type bulk_;
// The number of bytes consumed from the buffer.
std::size_t consumed_ = 0;
std::size_t consumed_;
// Returns the number of bytes that have been consumed.
auto consume_impl(type t, std::string_view elem, system::error_code& ec) -> node_type;
@@ -71,8 +70,13 @@ public:
auto get_consumed() const noexcept -> std::size_t;
auto consume(std::string_view view, system::error_code& ec) noexcept -> result;
void reset();
};
// Returns false if more data is needed. If true is returned the
// parser is either done or an error occured, that can be checked on
// ec.
template <class Adapter>
bool
parse(

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -9,12 +9,14 @@
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/system.hpp>
#include <vector>
#include <string>
#include <tuple>
namespace boost::redis {
namespace boost::redis
{
/** @brief Response with compile-time size.
* @ingroup high-level-api
@@ -32,6 +34,47 @@ using response = std::tuple<adapter::result<Ts>...>;
*/
using generic_response = adapter::result<std::vector<resp3::node>>;
} // boost::redis::resp3
/** @brief Consume on response from a generic response
*
* This function rotates the elements so that the start of the next
* response becomes the new front element. For example the output of
* the following code
*
* @code
* request req;
* req.push("PING", "one");
* req.push("PING", "two");
* req.push("PING", "three");
*
* generic_response resp;
* co_await conn->async_exec(req, resp, asio::deferred);
*
* std::cout << "PING: " << resp.value().front().value << std::endl;
* consume_one(resp);
* std::cout << "PING: " << resp.value().front().value << std::endl;
* consume_one(resp);
* std::cout << "PING: " << resp.value().front().value << std::endl;
* @endcode
*
* is
*
* @code
* PING: one
* PING: two
* PING: three
* @endcode
*
* Given that this function rotates elements, it won't be very
* efficient for responses with a large number of elements. It was
* introduced mainly to deal with buffers server pushes as shown in
* the cpp20_subscriber.cpp example. In the future queue-like
* responses might be introduced to consume in O(1) operations.
*/
void consume_one(generic_response& r, system::error_code& ec);
/// Throwing overload of `consume_one`.
void consume_one(generic_response& r);
} // boost::redis
#endif // BOOST_REDIS_RESPONSE_HPP

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -9,6 +9,7 @@
#include <boost/redis/impl/request.ipp>
#include <boost/redis/impl/ignore.ipp>
#include <boost/redis/impl/connection.ipp>
#include <boost/redis/impl/response.ipp>
#include <boost/redis/resp3/impl/type.ipp>
#include <boost/redis/resp3/impl/parser.ipp>
#include <boost/redis/resp3/impl/serialization.ipp>

View File

@@ -0,0 +1,43 @@
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_USAGE_HPP
#define BOOST_REDIS_USAGE_HPP
namespace boost::redis
{
/** @brief Connection usage information.
* @ingroup high-level-api
*
* @note: To simplify the implementation, the commands_sent and
* bytes_sent in the struct below are computed just before writing to
* the socket, which means on error they might not represent exaclty
* what has been received by the Redis server.
*/
struct usage {
/// Number of commands sent.
std::size_t commands_sent = 0;
/// Number of bytes sent.
std::size_t bytes_sent = 0;
/// Number of responses received.
std::size_t responses_received = 0;
/// Number of pushes received.
std::size_t pushes_received = 0;
/// Number of response-bytes received.
std::size_t response_bytes_received = 0;
/// Number of push-bytes received.
std::size_t push_bytes_received = 0;
};
} // boost::redis
#endif // BOOST_REDIS_USAGE_HPP

24
index.html Normal file
View File

@@ -0,0 +1,24 @@
<html>
<head>
<title>Boost.Redis</title>
<meta http-equiv="refresh" content="0; URL=./doc/html/index.html">
</head>
<body>
Automatic redirection failed, please go to
<a href="./doc/html/index.html">./doc/html/index.html</a>
<hr>
<tt>
Boost.Redis<br>
<br>
Copyright&nbsp;(C)&nbsp;2023&nbsp;Marcelo&nbsp;Zimbres<br>
<br>
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
<a href=http://www.boost.org/LICENSE_1_0.txt>http://www.boost.org/LICENSE_1_0.txt</a>) <br>
<br>
</tt>
</body>
</html>

16
meta/libraries.json Normal file
View File

@@ -0,0 +1,16 @@
{
"key": "redis",
"name": "Redis",
"authors": [
"Marcelo Zimbres Silva"
],
"description": "Redis async client library built on top of Boost.Asio.",
"category": [
"Concurrent",
"IO"
],
"maintainers": [
"Marcelo Zimbres Silva <mzimbres@gmail.com>"
],
"cxxstd": "17"
}

73
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,73 @@
# Common utilities
add_library(boost_redis_project_options INTERFACE)
target_link_libraries(boost_redis_project_options INTERFACE boost_redis)
if (MSVC)
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
endif()
add_library(boost_redis_src STATIC boost_redis.cpp)
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
# Test utils
add_library(boost_redis_tests_common STATIC common.cpp)
target_compile_features(boost_redis_tests_common PRIVATE cxx_std_17)
target_link_libraries(boost_redis_tests_common PRIVATE boost_redis_project_options)
macro(make_test TEST_NAME STANDARD)
set(EXE_NAME "boost_redis_${TEST_NAME}")
add_executable(${EXE_NAME} ${TEST_NAME}.cpp)
target_link_libraries(${EXE_NAME} PRIVATE
boost_redis_src
boost_redis_tests_common
boost_redis_project_options
)
target_compile_features(${EXE_NAME} PRIVATE cxx_std_${STANDARD})
add_test(${EXE_NAME} ${EXE_NAME})
endmacro()
make_test(test_conn_quit 17)
make_test(test_conn_tls 17)
make_test(test_low_level 17)
make_test(test_conn_exec_retry 17)
make_test(test_conn_exec_error 17)
make_test(test_request 17)
make_test(test_run 17)
make_test(test_low_level_sync_sans_io 17)
make_test(test_conn_check_health 17)
make_test(test_conn_exec 20)
make_test(test_conn_push 20)
make_test(test_conn_reconnect 20)
make_test(test_conn_exec_cancel 20)
make_test(test_conn_exec_cancel2 20)
make_test(test_conn_echo_stress 20)
make_test(test_conn_run_cancel 20)
make_test(test_issue_50 20)
# Coverage
set(
COVERAGE_TRACE_COMMAND
lcov --capture
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
--directory "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/include/*"
)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
--prefix "${PROJECT_SOURCE_DIR}"
--output-directory "${PROJECT_BINARY_DIR}/coverage_html"
)
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)

62
test/Jamfile Normal file
View File

@@ -0,0 +1,62 @@
import ../config/checks/config : requires ;
import ac ;
# Configure openssl if it hasn't been done yet
using openssl ;
# Use these requirements as both regular and usage requirements across all tests
local requirements =
<define>BOOST_ASIO_NO_DEPRECATED=1
<define>BOOST_ASIO_DISABLE_BOOST_ARRAY=1
<define>BOOST_ASIO_DISABLE_BOOST_BIND=1
<define>BOOST_ASIO_DISABLE_BOOST_DATE_TIME=1
<define>BOOST_ASIO_DISABLE_BOOST_REGEX=1
<toolset>msvc:<cxxflags>"/bigobj"
<target-os>windows:<define>_WIN32_WINNT=0x0601
<include>../include
[ requires
cxx14_constexpr
cxx14_generic_lambdas
cxx14_initialized_lambda_captures
cxx14_aggregate_nsdmi
cxx14_return_type_deduction
cxx17_hdr_charconv
cxx17_hdr_optional
cxx17_hdr_string_view
cxx17_hdr_variant
cxx17_std_apply
cxx17_structured_bindings
]
[ ac.check-library /openssl//ssl : <library>/openssl//ssl/<link>shared : <build>no ]
[ ac.check-library /openssl//crypto : <library>/openssl//crypto/<link>shared : <build>no ]
;
# Helper library
lib redis_test_common
:
boost_redis.cpp
common.cpp
: requirements $(requirements)
: usage-requirements $(requirements)
;
# B2 runs tests in parallel, and some tests rely on having exclusive
# access to a Redis server, so we only run the ones that don't require a DB server.
local tests =
test_low_level_sync_sans_io
test_low_level
test_request
test_run
;
# Build and run the tests
for local test in $(tests)
{
run
$(test).cpp
redis_test_common/<link>static
: target-name $(test)
;
}

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.5...3.22)
project(boost_redis_b2_test LANGUAGES CXX)
find_package(Boost REQUIRED COMPONENTS headers)
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE Boost::headers Threads::Threads OpenSSL::Crypto OpenSSL::SSL)
include(CTest)
add_test(NAME main COMMAND main)

View File

@@ -0,0 +1,15 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/src.hpp>
int main()
{
boost::redis::connection conn(boost::asio::system_executor{});
return static_cast<int>(!conn.will_reconnect());
}

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.5...3.22)
project(cmake_install_test LANGUAGES CXX)
find_package(boost_redis REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE Boost::redis)
include(CTest)
add_test(main main)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $<CONFIG>)

View File

@@ -0,0 +1,15 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/src.hpp>
int main()
{
boost::redis::connection conn(boost::asio::system_executor{});
return static_cast<int>(!conn.will_reconnect());
}

View File

@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.5...3.22)
project(cmake_subdir_test LANGUAGES CXX)
# Generated by boostdep --brief redis
set(_DEPENDENCIES
# Primary dependencies
asio
assert
core
mp11
system
throw_exception
# Secondary dependencies
align
array
bind
chrono
config
context
coroutine
date_time
exception
"function"
regex
smart_ptr
type_traits
utility
static_assert
variant2
winapi
integer
move
mpl
predef
ratio
typeof
pool
algorithm
io
lexical_cast
numeric/conversion
range
tokenizer
tuple
preprocessor
concept_check
container_hash
iterator
unordered
describe
container
conversion
detail
optional
rational
intrusive
function_types
fusion
functional
)
# Build our dependencies, so the targets Boost::xxx are defined
set(_BOOST_ROOT ../../../..)
foreach(_DEPENDENCY IN LISTS _DEPENDENCIES)
add_subdirectory(${_BOOST_ROOT}/libs/${_DEPENDENCY} boostorg/${_DEPENDENCY})
endforeach()
# Build our project
add_subdirectory(${_BOOST_ROOT}/libs/redis boostorg/redis)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE Boost::redis)
include(CTest)
add_test(main main)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $<CONFIG>)

View File

@@ -0,0 +1,15 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/src.hpp>
int main()
{
boost::redis::connection conn(boost::asio::system_executor{});
return static_cast<int>(!conn.will_reconnect());
}

View File

@@ -3,7 +3,6 @@
#include <boost/asio/consign.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/test/unit_test.hpp>
namespace net = boost::asio;
@@ -15,7 +14,6 @@ struct run_callback {
void operator()(boost::system::error_code const& ec) const
{
std::cout << "async_run: " << ec.message() << std::endl;
//BOOST_CHECK_EQUAL(ec, expected);
conn->cancel(op);
}
};

View File

@@ -21,5 +21,5 @@ run(
boost::redis::config cfg = {},
boost::system::error_code ec = boost::asio::error::operation_aborted,
boost::redis::operation op = boost::redis::operation::receive,
boost::redis::logger::level l = boost::redis::logger::level::info);
boost::redis::logger::level l = boost::redis::logger::level::disabled);

View File

@@ -5,6 +5,7 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/response.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE check-health
#include <boost/test/included/unit_test.hpp>
@@ -20,12 +21,11 @@ using boost::redis::request;
using boost::redis::ignore;
using boost::redis::operation;
using boost::redis::generic_response;
using boost::redis::logger;
using boost::redis::consume_one;
using redis::config;
// TODO: Test cancel(health_check)
std::chrono::seconds const interval{1};
struct push_callback {
connection* conn1;
@@ -39,9 +39,8 @@ struct push_callback {
{
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
resp2->value().clear();
BOOST_ASIO_CORO_YIELD
conn2->async_receive(*resp2, *this);
conn2->async_receive(*this);
if (ec) {
std::clog << "Exiting." << std::endl;
return;
@@ -50,6 +49,7 @@ struct push_callback {
BOOST_TEST(resp2->has_value());
BOOST_TEST(!resp2->value().empty());
std::clog << "Event> " << resp2->value().front().value << std::endl;
consume_one(*resp2);
++i;
@@ -106,6 +106,7 @@ BOOST_AUTO_TEST_CASE(check_health)
request req2;
req2.push("MONITOR");
generic_response resp2;
conn2.set_receive_response(resp2);
conn2.async_exec(req2, ignore, [](auto ec, auto) {
std::cout << "async_exec: " << std::endl;
@@ -113,7 +114,7 @@ BOOST_AUTO_TEST_CASE(check_health)
});
//--------------------------------
push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes.
ioc.run();

View File

@@ -26,12 +26,39 @@ using boost::redis::ignore_t;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::connection;
using boost::redis::usage;
using boost::redis::error;
std::ostream& operator<<(std::ostream& os, usage const& u)
{
os
<< "Commands sent: " << u.commands_sent << "\n"
<< "Bytes sent: " << u.bytes_sent << "\n"
<< "Responses received: " << u.responses_received << "\n"
<< "Pushes received: " << u.pushes_received << "\n"
<< "Response bytes received: " << u.response_bytes_received << "\n"
<< "Push bytes received: " << u.push_bytes_received;
return os;
}
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void>
{
int c = 0;
for (;;) {
co_await conn->async_receive(ignore, net::use_awaitable);
for (error_code ec;;) {
conn->receive(ec);
if (ec == error::sync_receive_push_failed) {
ec = {};
co_await conn->async_receive(redirect_error(net::use_awaitable, ec));
} else if (!ec) {
//std::cout << "Skipping suspension." << std::endl;
}
if (ec) {
BOOST_TEST(false);
std::cout << "push_consumer error: " << ec.message() << std::endl;
co_return;
}
if (++c == expected)
break;
}
@@ -43,36 +70,15 @@ auto
echo_session(
std::shared_ptr<connection> conn,
std::shared_ptr<request> pubs,
std::string id,
int n) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
request req;
response<ignore_t, std::string, ignore_t> resp;
for (auto i = 0; i < n; ++i) {
auto const msg = id + "/" + std::to_string(i);
//std::cout << msg << std::endl;
req.push("HELLO", 3); // Just to mess around.
req.push("PING", msg);
req.push("PING", "lsls"); // TODO: Change to HELLO after fixing issue 105.
boost::system::error_code ec;
co_await conn->async_exec(req, resp, redir(ec));
BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{});
BOOST_REQUIRE_EQUAL(msg, std::get<1>(resp).value());
req.clear();
std::get<1>(resp).value().clear();
for (auto i = 0; i < n; ++i)
co_await conn->async_exec(*pubs, ignore, net::deferred);
}
}
auto async_echo_stress() -> net::awaitable<void>
auto async_echo_stress(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
config cfg;
cfg.health_check_interval = std::chrono::seconds::zero();
run(conn, cfg,
@@ -86,19 +92,20 @@ auto async_echo_stress() -> net::awaitable<void>
// Number of coroutines that will send pings sharing the same
// connection to redis.
int const sessions = 500;
int const sessions = 1000;
// The number of pings that will be sent by each session.
int const msgs = 1000;
int const msgs = 500;
// The number of publishes that will be sent by each session with
// each message.
int const n_pubs = 10;
int const n_pubs = 100;
// This is the total number of pushes we will receive.
int total_pushes = sessions * msgs * n_pubs + 1;
auto pubs = std::make_shared<request>();
pubs->push("PING");
for (int i = 0; i < n_pubs; ++i)
pubs->push("PUBLISH", "channel", "payload");
@@ -107,14 +114,20 @@ auto async_echo_stress() -> net::awaitable<void>
net::co_spawn(ex, push_consumer(conn, total_pushes), net::detached);
for (int i = 0; i < sessions; ++i)
net::co_spawn(ex, echo_session(conn, pubs, std::to_string(i), msgs), net::detached);
net::co_spawn(ex, echo_session(conn, pubs, msgs), net::detached);
}
BOOST_AUTO_TEST_CASE(echo_stress)
{
net::io_context ioc;
net::co_spawn(ioc, async_echo_stress(), net::detached);
auto conn = std::make_shared<connection>(ioc);
net::co_spawn(ioc, async_echo_stress(conn), net::detached);
ioc.run();
std::cout
<< "-------------------\n"
<< conn->get_usage()
<< std::endl;
}
#else

View File

@@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->async_exec(req1, ignore, [&](auto ec, auto){
// Second callback to the called.
std::cout << "req1" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_TEST(!ec);
BOOST_TEST(!seen2);
BOOST_TEST(seen3);
seen1 = true;
@@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->async_exec(req2, ignore, [&](auto ec, auto){
// Last callback to the called.
std::cout << "req2" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_TEST(!ec);
BOOST_TEST(seen1);
BOOST_TEST(seen3);
seen2 = true;
@@ -73,7 +73,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->async_exec(req3, ignore, [&](auto ec, auto){
// Callback that will be called first.
std::cout << "req3" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_TEST(!ec);
BOOST_TEST(!seen1);
BOOST_TEST(!seen2);
seen3 = true;

View File

@@ -242,6 +242,8 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
conn->async_exec(req1, ignore, c1);
generic_response gresp;
conn->set_receive_response(gresp);
auto c3 = [&](auto ec, auto)
{
std::cout << "async_receive" << std::endl;
@@ -254,7 +256,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
conn->cancel(operation::reconnection);
};
conn->async_receive(gresp, c3);
conn->async_receive(c3);
run(conn);

View File

@@ -26,6 +26,7 @@ using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::system::error_code;
using redis::config;
using boost::redis::logger;
using namespace std::chrono_literals;
@@ -49,7 +50,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
auto c3 =[](auto ec, auto...)
{
BOOST_TEST(!!ec);
std::cout << "c3: " << ec.message() << std::endl;
};
auto c2 =[&, conn](auto ec, auto...)
@@ -69,12 +70,11 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
run(conn, {}, {});
bool push_received = false;
conn->async_receive(ignore, [&, conn](auto ec, auto){
conn->async_receive([&, conn](auto ec, auto){
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
push_received = true;
conn->cancel();
});
ioc.run();
@@ -87,29 +87,45 @@ BOOST_AUTO_TEST_CASE(push_received1)
net::io_context ioc;
auto conn = std::make_shared<connection>(ioc);
// Trick: Uses SUBSCRIBE because this command has no response or
// better said, its response is a server push, which is what we
// want to test. We send two because we want to test both
// async_receive and receive.
request req;
//req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
req.push("SUBSCRIBE", "channel1");
req.push("SUBSCRIBE", "channel2");
conn->async_exec(req, ignore, [conn](auto ec, auto){
std::cout << "async_exec" << std::endl;
BOOST_TEST(!ec);
});
run(conn);
bool push_async_received = false;
conn->async_receive([&, conn](auto ec, auto){
std::cout << "(1) async_receive" << std::endl;
bool push_received = false;
conn->async_receive(ignore, [&, conn](auto ec, auto){
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
push_received = true;
push_async_received = true;
// Receives the second push synchronously.
error_code ec2;
std::size_t res = 0;
res = conn->receive(ec2);
BOOST_TEST(!ec2);
BOOST_TEST(res != std::size_t(0));
// Tries to receive a third push synchronously.
ec2 = {};
res = conn->receive(ec2);
BOOST_CHECK_EQUAL(ec2, boost::redis::make_error_code(boost::redis::error::sync_receive_push_failed));
conn->cancel();
});
run(conn);
ioc.run();
BOOST_TEST(push_received);
BOOST_TEST(push_async_received);
}
BOOST_AUTO_TEST_CASE(push_filtered_out)
@@ -128,7 +144,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
BOOST_TEST(!ec);
});
conn->async_receive(ignore, [conn](auto ec, auto){
conn->async_receive([&, conn](auto ec, auto){
BOOST_TEST(!ec);
conn->cancel(operation::reconnection);
});
@@ -146,12 +162,12 @@ net::awaitable<void>
push_consumer1(std::shared_ptr<connection> conn, bool& push_received)
{
{
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable));
BOOST_TEST(!ec);
}
{
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable));
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
}
@@ -190,8 +206,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
req.push("SUBSCRIBE", "channel");
req.push("PING");
conn->async_receive(error_tag_obj, [conn](auto ec, auto) {
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
conn->set_receive_response(error_tag_obj);
conn->async_receive([&, conn](auto ec, auto) {
BOOST_CHECK_EQUAL(ec, boost::asio::experimental::error::channel_cancelled);
conn->cancel(operation::reconnection);
});
@@ -199,7 +217,9 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
});
run(conn);
conn->async_run({}, {}, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
});
ioc.run();
@@ -210,7 +230,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
net::awaitable<void> push_consumer3(std::shared_ptr<connection> conn)
{
for (;;) {
co_await conn->async_receive(ignore, net::use_awaitable);
co_await conn->async_receive(net::use_awaitable);
}
}

View File

@@ -17,6 +17,7 @@ using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
using boost::redis::operation;
using boost::system::error_code;
bool verify_certificate(bool, net::ssl::verify_context&)
{
@@ -32,6 +33,7 @@ BOOST_AUTO_TEST_CASE(ping)
cfg.password = "aedis";
cfg.addr.host = "db.occase.de";
cfg.addr.port = "6380";
//cfg.health_check_interval = std::chrono::seconds{0};
std::string const in = "Kabuf";
@@ -55,6 +57,44 @@ BOOST_AUTO_TEST_CASE(ping)
ioc.run();
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
std::cout << "===============================" << std::endl;
}
BOOST_AUTO_TEST_CASE(acl_does_not_allow_select)
{
config cfg;
cfg.use_ssl = true;
cfg.username = "aedis";
cfg.password = "aedis";
cfg.addr.host = "db.occase.de";
cfg.addr.port = "6380";
cfg.database_index = 22;
cfg.reconnect_wait_interval = std::chrono::seconds::zero();
std::string const in = "Kabuf";
request req;
req.push("PING", in);
response<std::string> resp;
net::io_context ioc;
connection conn{ioc};
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
conn.next_layer().set_verify_callback(verify_certificate);
conn.async_exec(req, resp, [&](auto, auto) {
// TODO: We should not need this cancel here because
// reconnect was disabled.
conn.cancel();
});
error_code ec2;
conn.async_run(cfg, {}, [&](auto ec) {
ec2 = ec;
});
ioc.run();
BOOST_TEST(!!ec2);
}

View File

@@ -47,11 +47,15 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
for (;;) {
std::cout << "aaaa" << std::endl;
error_code ec;
co_await conn->async_receive(ignore, redirect_error(use_awaitable, ec));
if (ec)
co_await conn->async_receive(redirect_error(use_awaitable, ec));
if (ec) {
std::cout << "Error in async_receive" << std::endl;
break;
}
}
}
std::cout << "Exiting the receiver." << std::endl;
}
auto

View File

@@ -7,16 +7,11 @@
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/detail/read.hpp>
#include <boost/system/errc.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/redis/resp3/parser.hpp>
#define BOOST_TEST_MODULE low level
#include <boost/test/included/unit_test.hpp>
#include <map>
#include <iostream>
#include <optional>
@@ -30,19 +25,22 @@ auto operator==(boost::redis::ignore_t, boost::redis::ignore_t) noexcept {return
auto operator!=(boost::redis::ignore_t, boost::redis::ignore_t) noexcept {return false;}
}
namespace net = boost::asio;
namespace redis = boost::redis;
namespace resp3 = boost::redis::resp3;
using boost::system::error_code;
using boost::redis::request;
using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::adapter::result;
using boost::redis::resp3::parser;
using boost::redis::resp3::parse;
using boost::redis::consume_one;
using boost::redis::error;
using test_stream = boost::beast::test::stream;
using boost::redis::adapter::adapt2;
using node_type = result<resp3::node>;
using vec_node_type = result<std::vector<resp3::node>>;
using vec_type = result<std::vector<std::string>>;
using op_vec_type = result<std::optional<std::vector<std::string>>>;
@@ -82,95 +80,51 @@ template <class Result>
struct expect {
std::string in;
Result expected;
boost::system::error_code ec{};
error_code ec{};
resp3::type error_type = resp3::type::invalid;
};
template <class Result>
auto make_expected(std::string in, Result expected, boost::system::error_code ec = {}, resp3::type error_type = resp3::type::invalid)
auto make_expected(std::string in, Result expected, error_code ec = {}, resp3::type error_type = resp3::type::invalid)
{
return expect<Result>{in, expected, ec, error_type};
}
template <class Result>
void test_sync(net::any_io_executor ex, expect<Result> e)
void test_sync(expect<Result> e)
{
std::string rbuffer;
test_stream ts {ex};
ts.append(e.in);
parser p;
Result result;
boost::system::error_code ec;
auto dbuf = net::dynamic_buffer(rbuffer);
auto const consumed = redis::detail::read(ts, dbuf, adapt2(result), ec);
if (e.ec) {
auto adapter = adapt2(result);
error_code ec;
auto const res = parse(p, e.in, adapter, ec);
BOOST_TEST(res); // None of these tests need more data.
if (ec) {
BOOST_CHECK_EQUAL(ec, e.ec);
return;
}
dbuf.consume(consumed);
BOOST_TEST(!ec);
BOOST_TEST(rbuffer.empty());
if (result.has_value()) {
auto const res = result == e.expected;
BOOST_TEST(res);
BOOST_TEST(bool(result == e.expected));
BOOST_CHECK_EQUAL(e.in.size(), p.get_consumed());
} else {
BOOST_TEST(result.has_error());
BOOST_CHECK_EQUAL(result.error().data_type, e.error_type);
}
}
template <class Result>
class async_test: public std::enable_shared_from_this<async_test<Result>> {
private:
std::string rbuffer_;
test_stream ts_;
expect<Result> data_;
Result result_;
public:
async_test(net::any_io_executor ex, expect<Result> e)
: ts_{ex}
, data_{e}
{
ts_.append(e.in);
}
void run()
{
auto self = this->shared_from_this();
auto f = [self](auto ec, auto)
{
if (self->data_.ec) {
BOOST_CHECK_EQUAL(ec, self->data_.ec);
return;
}
BOOST_TEST(!ec);
//BOOST_TEST(self->rbuffer_.empty());
if (self->result_.has_value()) {
auto const res = self->result_ == self->data_.expected;
BOOST_TEST(res);
} else {
BOOST_TEST(self->result_.has_error());
BOOST_CHECK_EQUAL(self->result_.error().data_type, self->data_.error_type);
}
};
redis::detail::async_read(
ts_,
net::dynamic_buffer(rbuffer_),
adapt2(result_),
f);
}
};
template <class Result>
void test_async(net::any_io_executor ex, expect<Result> e)
void test_sync2(expect<Result> e)
{
std::make_shared<async_test<Result>>(ex, e)->run();
parser p;
Result result;
auto adapter = adapt2(result);
error_code ec;
auto const res = parse(p, e.in, adapter, ec);
BOOST_TEST(res); // None of these tests need more data.
BOOST_CHECK_EQUAL(ec, e.ec);
}
auto make_blob()
@@ -202,7 +156,7 @@ result<std::optional<bool>> op_bool_ok = true;
// TODO: Test a streamed string that is not finished with a string of
// size 0 but other command comes in.
vec_node_type streamed_string_e1
generic_response streamed_string_e1
{{ {boost::redis::resp3::type::streamed_string, 0, 1, ""}
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "Hell"}
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "o wor"}
@@ -210,10 +164,10 @@ vec_node_type streamed_string_e1
, {boost::redis::resp3::type::streamed_string_part, 1, 1, ""}
}};
vec_node_type streamed_string_e2
generic_response streamed_string_e2
{{{resp3::type::streamed_string, 0UL, 1UL, {}}, {resp3::type::streamed_string_part, 1UL, 1UL, {}} }};
vec_node_type const push_e1a
generic_response const push_e1a
{{ {resp3::type::push, 4UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, "pubsub"}
, {resp3::type::simple_string, 1UL, 1UL, "message"}
@@ -221,10 +175,10 @@ vec_node_type const push_e1a
, {resp3::type::simple_string, 1UL, 1UL, "some message"}
}};
vec_node_type const push_e1b
generic_response const push_e1b
{{{resp3::type::push, 0UL, 0UL, {}}}};
vec_node_type const set_expected1a
generic_response const set_expected1a
{{{resp3::type::set, 6UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
@@ -240,7 +194,7 @@ muset_type const set_e1g{{"apple", "one", "orange", "orange", "three", "two"}};
vec_type const set_e1d = {{"orange", "apple", "one", "two", "three", "orange"}};
op_vec_type const set_expected_1e = set_e1d;
vec_node_type const array_e1a
generic_response const array_e1a
{{ {resp3::type::array, 3UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
@@ -250,12 +204,12 @@ vec_node_type const array_e1a
result<std::vector<int>> const array_e1b{{11, 22, 3}};
result<std::vector<std::string>> const array_e1c{{"11", "22", "3"}};
result<std::vector<std::string>> const array_e1d{};
vec_node_type const array_e1e{{{resp3::type::array, 0UL, 0UL, {}}}};
generic_response const array_e1e{{{resp3::type::array, 0UL, 0UL, {}}}};
array_type const array_e1f{{11, 22, 3}};
result<std::list<int>> const array_e1g{{11, 22, 3}};
result<std::deque<int>> const array_e1h{{11, 22, 3}};
vec_node_type const map_expected_1a
generic_response const map_expected_1a
{{ {resp3::type::map, 4UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
@@ -311,7 +265,7 @@ tuple8_type const map_e1f
, std::string{"key3"}, std::string{"value3"}
};
vec_node_type const attr_e1a
generic_response const attr_e1a
{{ {resp3::type::attribute, 1UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
, {resp3::type::map, 2UL, 1UL, {}}
@@ -321,7 +275,7 @@ vec_node_type const attr_e1a
, {resp3::type::doublean, 1UL, 2UL, "0.0012"}
} };
vec_node_type const attr_e1b
generic_response const attr_e1b
{{{resp3::type::attribute, 0UL, 0UL, {}} }};
#define S01a "#11\r\n"
@@ -397,173 +351,136 @@ vec_node_type const attr_e1b
#define S18d "$0\r\n\r\n"
#define NUMBER_TEST_CONDITIONS(test) \
test(ex, make_expected(S01a, result<std::optional<bool>>{}, boost::redis::error::unexpected_bool_value)); \
test(ex, make_expected(S01b, result<bool>{{false}})); \
test(ex, make_expected(S01b, node_type{{resp3::type::boolean, 1UL, 0UL, {"f"}}})); \
test(ex, make_expected(S01c, result<bool>{{true}})); \
test(ex, make_expected(S01c, node_type{{resp3::type::boolean, 1UL, 0UL, {"t"}}})); \
test(ex, make_expected(S01c, op_bool_ok)); \
test(ex, make_expected(S01c, result<std::map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S01c, result<std::set<int>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S01c, result<std::unordered_map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S01c, result<std::unordered_set<int>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S02a, streamed_string_e2)); \
test(ex, make_expected(S03a, result<int>{}, boost::redis::error::expects_resp3_simple_type));\
test(ex, make_expected(S03a, result<std::optional<int>>{}, boost::redis::error::expects_resp3_simple_type));; \
test(ex, make_expected(S02b, result<int>{}, boost::redis::error::not_a_number)); \
test(ex, make_expected(S02b, result<std::string>{std::string{"Hello word"}})); \
test(ex, make_expected(S02b, streamed_string_e1)); \
test(ex, make_expected(S02c, result<std::string>{}, boost::redis::error::not_a_number)); \
test(ex, make_expected(S05a, node_type{{resp3::type::number, 1UL, 0UL, {"-3"}}})); \
test(ex, make_expected(S05b, result<int>{11})); \
test(ex, make_expected(S05b, op_int_ok)); \
test(ex, make_expected(S05b, result<std::list<std::string>>{}, boost::redis::error::expects_resp3_aggregate)); \
test(ex, make_expected(S05b, result<std::map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S05b, result<std::set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S05b, result<std::unordered_map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S05b, result<std::unordered_set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(s05c, array_type2{}, boost::redis::error::expects_resp3_aggregate));\
test(ex, make_expected(s05c, node_type{{resp3::type::number, 1UL, 0UL, {"3"}}}));\
test(ex, make_expected(S06a, op_type_01{})); \
test(ex, make_expected(S06a, op_type_02{}));\
test(ex, make_expected(S06a, op_type_03{}));\
test(ex, make_expected(S06a, op_type_04{}));\
test(ex, make_expected(S06a, op_type_05{}));\
test(ex, make_expected(S06a, op_type_06{}));\
test(ex, make_expected(S06a, op_type_07{}));\
test(ex, make_expected(S06a, op_type_08{}));\
test(ex, make_expected(S06a, op_type_09{}));\
test(ex, make_expected(S07a, push_e1a)); \
test(ex, make_expected(S07b, push_e1b)); \
test(ex, make_expected(S04b, map_type{}, boost::redis::error::expects_resp3_map));\
test(ex, make_expected(S03b, map_e1f));\
test(ex, make_expected(S03b, map_e1g));\
test(ex, make_expected(S03b, map_e1k));\
test(ex, make_expected(S03b, map_expected_1a));\
test(ex, make_expected(S03b, map_expected_1b));\
test(ex, make_expected(S03b, map_expected_1c));\
test(ex, make_expected(S03b, map_expected_1d));\
test(ex, make_expected(S03b, map_expected_1e));\
test(ex, make_expected(S08a, attr_e1a)); \
test(ex, make_expected(S08b, attr_e1b)); \
test(ex, make_expected(S04e, array_e1a));\
test(ex, make_expected(S04e, array_e1b));\
test(ex, make_expected(S04e, array_e1c));\
test(ex, make_expected(S04e, array_e1f));\
test(ex, make_expected(S04e, array_e1g));\
test(ex, make_expected(S04e, array_e1h));\
test(ex, make_expected(S04e, array_type2{}, boost::redis::error::incompatible_size));\
test(ex, make_expected(S04e, tuple_int_2{}, boost::redis::error::incompatible_size));\
test(ex, make_expected(S04f, array_type2{}, boost::redis::error::nested_aggregate_not_supported));\
test(ex, make_expected(S04g, vec_node_type{}, boost::redis::error::exceeeds_max_nested_depth));\
test(ex, make_expected(S04h, array_e1d));\
test(ex, make_expected(S04h, array_e1e));\
test(ex, make_expected(S04i, set_type{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S09a, set_e1c)); \
test(ex, make_expected(S09a, set_e1d)); \
test(ex, make_expected(S09a, set_e1f)); \
test(ex, make_expected(S09a, set_e1g)); \
test(ex, make_expected(S09a, set_expected1a)); \
test(ex, make_expected(S09a, set_expected_1e)); \
test(ex, make_expected(S09a, set_type{{"apple", "one", "orange", "three", "two"}})); \
test(ex, make_expected(S09b, vec_node_type{{{resp3::type::set, 0UL, 0UL, {}}}})); \
test(ex, make_expected(S03c, map_type{}));\
test(ex, make_expected(S11a, node_type{{resp3::type::doublean, 1UL, 0UL, {"1.23"}}}));\
test(ex, make_expected(S11b, node_type{{resp3::type::doublean, 1UL, 0UL, {"inf"}}}));\
test(ex, make_expected(S11c, node_type{{resp3::type::doublean, 1UL, 0UL, {"-inf"}}}));\
test(ex, make_expected(S11d, result<double>{{1.23}}));\
test(ex, make_expected(S11e, result<double>{{0}}, boost::redis::error::not_a_double));\
test(ex, make_expected(S13a, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}}));\
test(ex, make_expected(S13b, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {}}}));\
test(ex, make_expected(S14a, node_type{{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}}));\
test(ex, make_expected(S14b, result<int>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S15a, result<std::optional<std::string>>{{"OK"}}));\
test(ex, make_expected(S15a, result<std::string>{{"OK"}}));\
test(ex, make_expected(S15b, result<std::optional<std::string>>{""}));\
test(ex, make_expected(S15b, result<std::string>{{""}}));\
test(ex, make_expected(S16a, result<int>{}, boost::redis::error::invalid_data_type));\
test(ex, make_expected(S05d, result<int>{11}, boost::redis::error::not_a_number));\
test(ex, make_expected(S03d, map_type{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S02d, result<std::string>{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S17a, result<std::string>{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S05e, result<int>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S01d, result<std::optional<bool>>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S11f, result<std::string>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S17b, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hh"}}}));\
test(ex, make_expected(S18c, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}}));\
test(ex, make_expected(S18d, node_type{{resp3::type::blob_string, 1UL, 0UL, {}}}));\
test(ex, make_expected(make_blob_string(blob), node_type{{resp3::type::blob_string, 1UL, 0UL, {blob}}}));\
test(ex, make_expected(S04a, result<std::vector<int>>{{11}})); \
test(ex, make_expected(S04d, result<response<std::unordered_set<std::string>>>{response<std::unordered_set<std::string>>{{set_e1c}}})); \
test(ex, make_expected(S04c, result<response<std::map<std::string, std::string>>>{response<std::map<std::string, std::string>>{{map_expected_1b}}}));\
test(ex, make_expected(S03b, map_e1l));\
test(ex, make_expected(S06a, result<int>{0}, {}, resp3::type::null)); \
test(ex, make_expected(S06a, map_type{}, {}, resp3::type::null));\
test(ex, make_expected(S06a, array_type{}, {}, resp3::type::null));\
test(ex, make_expected(S06a, result<std::list<int>>{}, {}, resp3::type::null));\
test(ex, make_expected(S06a, result<std::vector<int>>{}, {}, resp3::type::null));\
test(ex, make_expected(S10a, result<ignore_t>{}, boost::redis::error::resp3_simple_error)); \
test(ex, make_expected(S10a, node_type{{resp3::type::simple_error, 1UL, 0UL, {"Error"}}}, {}, resp3::type::simple_error)); \
test(ex, make_expected(S10b, node_type{{resp3::type::simple_error, 1UL, 0UL, {""}}}, {}, resp3::type::simple_error)); \
test(ex, make_expected(S12a, node_type{{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}}, {}, resp3::type::blob_error));\
test(ex, make_expected(S12b, node_type{{resp3::type::blob_error, 1UL, 0UL, {}}}, {}, resp3::type::blob_error));\
test(ex, make_expected(S12c, result<ignore_t>{}, boost::redis::error::resp3_blob_error));\
test(make_expected(S01a, result<std::optional<bool>>{}, boost::redis::error::unexpected_bool_value)); \
test(make_expected(S01b, result<bool>{{false}})); \
test(make_expected(S01b, node_type{{resp3::type::boolean, 1UL, 0UL, {"f"}}})); \
test(make_expected(S01c, result<bool>{{true}})); \
test(make_expected(S01c, node_type{{resp3::type::boolean, 1UL, 0UL, {"t"}}})); \
test(make_expected(S01c, op_bool_ok)); \
test(make_expected(S01c, result<std::map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
test(make_expected(S01c, result<std::set<int>>{}, boost::redis::error::expects_resp3_set)); \
test(make_expected(S01c, result<std::unordered_map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
test(make_expected(S01c, result<std::unordered_set<int>>{}, boost::redis::error::expects_resp3_set)); \
test(make_expected(S02a, streamed_string_e2)); \
test(make_expected(S03a, result<int>{}, boost::redis::error::expects_resp3_simple_type));\
test(make_expected(S03a, result<std::optional<int>>{}, boost::redis::error::expects_resp3_simple_type));; \
test(make_expected(S02b, result<int>{}, boost::redis::error::not_a_number)); \
test(make_expected(S02b, result<std::string>{std::string{"Hello word"}})); \
test(make_expected(S02b, streamed_string_e1)); \
test(make_expected(S02c, result<std::string>{}, boost::redis::error::not_a_number)); \
test(make_expected(S05a, node_type{{resp3::type::number, 1UL, 0UL, {"-3"}}})); \
test(make_expected(S05b, result<int>{11})); \
test(make_expected(S05b, op_int_ok)); \
test(make_expected(S05b, result<std::list<std::string>>{}, boost::redis::error::expects_resp3_aggregate)); \
test(make_expected(S05b, result<std::map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
test(make_expected(S05b, result<std::set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
test(make_expected(S05b, result<std::unordered_map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
test(make_expected(S05b, result<std::unordered_set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
test(make_expected(s05c, array_type2{}, boost::redis::error::expects_resp3_aggregate));\
test(make_expected(s05c, node_type{{resp3::type::number, 1UL, 0UL, {"3"}}}));\
test(make_expected(S06a, op_type_01{})); \
test(make_expected(S06a, op_type_02{}));\
test(make_expected(S06a, op_type_03{}));\
test(make_expected(S06a, op_type_04{}));\
test(make_expected(S06a, op_type_05{}));\
test(make_expected(S06a, op_type_06{}));\
test(make_expected(S06a, op_type_07{}));\
test(make_expected(S06a, op_type_08{}));\
test(make_expected(S06a, op_type_09{}));\
test(make_expected(S07a, push_e1a)); \
test(make_expected(S07b, push_e1b)); \
test(make_expected(S04b, map_type{}, boost::redis::error::expects_resp3_map));\
test(make_expected(S03b, map_e1f));\
test(make_expected(S03b, map_e1g));\
test(make_expected(S03b, map_e1k));\
test(make_expected(S03b, map_expected_1a));\
test(make_expected(S03b, map_expected_1b));\
test(make_expected(S03b, map_expected_1c));\
test(make_expected(S03b, map_expected_1d));\
test(make_expected(S03b, map_expected_1e));\
test(make_expected(S08a, attr_e1a)); \
test(make_expected(S08b, attr_e1b)); \
test(make_expected(S04e, array_e1a));\
test(make_expected(S04e, array_e1b));\
test(make_expected(S04e, array_e1c));\
test(make_expected(S04e, array_e1f));\
test(make_expected(S04e, array_e1g));\
test(make_expected(S04e, array_e1h));\
test(make_expected(S04e, array_type2{}, boost::redis::error::incompatible_size));\
test(make_expected(S04e, tuple_int_2{}, boost::redis::error::incompatible_size));\
test(make_expected(S04f, array_type2{}, boost::redis::error::nested_aggregate_not_supported));\
test(make_expected(S04g, generic_response{}, boost::redis::error::exceeeds_max_nested_depth));\
test(make_expected(S04h, array_e1d));\
test(make_expected(S04h, array_e1e));\
test(make_expected(S04i, set_type{}, boost::redis::error::expects_resp3_set)); \
test(make_expected(S09a, set_e1c)); \
test(make_expected(S09a, set_e1d)); \
test(make_expected(S09a, set_e1f)); \
test(make_expected(S09a, set_e1g)); \
test(make_expected(S09a, set_expected1a)); \
test(make_expected(S09a, set_expected_1e)); \
test(make_expected(S09a, set_type{{"apple", "one", "orange", "three", "two"}})); \
test(make_expected(S09b, generic_response{{{resp3::type::set, 0UL, 0UL, {}}}})); \
test(make_expected(S03c, map_type{}));\
test(make_expected(S11a, node_type{{resp3::type::doublean, 1UL, 0UL, {"1.23"}}}));\
test(make_expected(S11b, node_type{{resp3::type::doublean, 1UL, 0UL, {"inf"}}}));\
test(make_expected(S11c, node_type{{resp3::type::doublean, 1UL, 0UL, {"-inf"}}}));\
test(make_expected(S11d, result<double>{{1.23}}));\
test(make_expected(S11e, result<double>{{0}}, boost::redis::error::not_a_double));\
test(make_expected(S13a, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}}));\
test(make_expected(S13b, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {}}}));\
test(make_expected(S14a, node_type{{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}}));\
test(make_expected(S14b, result<int>{}, boost::redis::error::empty_field));\
test(make_expected(S15a, result<std::optional<std::string>>{{"OK"}}));\
test(make_expected(S15a, result<std::string>{{"OK"}}));\
test(make_expected(S15b, result<std::optional<std::string>>{""}));\
test(make_expected(S15b, result<std::string>{{""}}));\
test(make_expected(S16a, result<int>{}, boost::redis::error::invalid_data_type));\
test(make_expected(S05d, result<int>{11}, boost::redis::error::not_a_number));\
test(make_expected(S03d, map_type{}, boost::redis::error::not_a_number));\
test(make_expected(S02d, result<std::string>{}, boost::redis::error::not_a_number));\
test(make_expected(S17a, result<std::string>{}, boost::redis::error::not_a_number));\
test(make_expected(S05e, result<int>{}, boost::redis::error::empty_field));\
test(make_expected(S01d, result<std::optional<bool>>{}, boost::redis::error::empty_field));\
test(make_expected(S11f, result<std::string>{}, boost::redis::error::empty_field));\
test(make_expected(S17b, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hh"}}}));\
test(make_expected(S18c, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}}));\
test(make_expected(S18d, node_type{{resp3::type::blob_string, 1UL, 0UL, {}}}));\
test(make_expected(make_blob_string(blob), node_type{{resp3::type::blob_string, 1UL, 0UL, {blob}}}));\
test(make_expected(S04a, result<std::vector<int>>{{11}})); \
test(make_expected(S04d, result<response<std::unordered_set<std::string>>>{response<std::unordered_set<std::string>>{{set_e1c}}})); \
test(make_expected(S04c, result<response<std::map<std::string, std::string>>>{response<std::map<std::string, std::string>>{{map_expected_1b}}}));\
test(make_expected(S03b, map_e1l));\
test(make_expected(S06a, result<int>{0}, {}, resp3::type::null)); \
test(make_expected(S06a, map_type{}, {}, resp3::type::null));\
test(make_expected(S06a, array_type{}, {}, resp3::type::null));\
test(make_expected(S06a, result<std::list<int>>{}, {}, resp3::type::null));\
test(make_expected(S06a, result<std::vector<int>>{}, {}, resp3::type::null));\
test(make_expected(S10a, result<ignore_t>{}, boost::redis::error::resp3_simple_error)); \
test(make_expected(S10a, node_type{{resp3::type::simple_error, 1UL, 0UL, {"Error"}}}, {}, resp3::type::simple_error)); \
test(make_expected(S10b, node_type{{resp3::type::simple_error, 1UL, 0UL, {""}}}, {}, resp3::type::simple_error)); \
test(make_expected(S12a, node_type{{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}}, {}, resp3::type::blob_error));\
test(make_expected(S12b, node_type{{resp3::type::blob_error, 1UL, 0UL, {}}}, {}, resp3::type::blob_error));\
test(make_expected(S12c, result<ignore_t>{}, boost::redis::error::resp3_blob_error));\
BOOST_AUTO_TEST_CASE(parser)
BOOST_AUTO_TEST_CASE(sansio)
{
net::io_context ioc;
auto ex = ioc.get_executor();
#define TEST test_sync
NUMBER_TEST_CONDITIONS(TEST)
#undef TEST
#define TEST test_async
NUMBER_TEST_CONDITIONS(TEST)
#undef TEST
ioc.run();
NUMBER_TEST_CONDITIONS(test_sync)
}
BOOST_AUTO_TEST_CASE(ignore_adapter_simple_error)
{
net::io_context ioc;
std::string rbuffer;
boost::system::error_code ec;
test_stream ts {ioc};
ts.append(S10a);
redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
BOOST_CHECK_EQUAL(ec, boost::redis::error::resp3_simple_error);
BOOST_TEST(!rbuffer.empty());
test_sync2(make_expected(S10a, ignore, boost::redis::error::resp3_simple_error));
}
BOOST_AUTO_TEST_CASE(ignore_adapter_blob_error)
{
net::io_context ioc;
std::string rbuffer;
boost::system::error_code ec;
test_stream ts {ioc};
ts.append(S12a);
redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
BOOST_CHECK_EQUAL(ec, boost::redis::error::resp3_blob_error);
BOOST_TEST(!rbuffer.empty());
test_sync2(make_expected(S12a, ignore, boost::redis::error::resp3_blob_error));
}
BOOST_AUTO_TEST_CASE(ignore_adapter_no_error)
{
net::io_context ioc;
std::string rbuffer;
boost::system::error_code ec;
test_stream ts {ioc};
ts.append(S05b);
auto const consumed = redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
BOOST_TEST(!ec);
BOOST_CHECK_EQUAL(rbuffer.size(), consumed);
test_sync2(make_expected(S05b, ignore));
}
//-----------------------------------------------------------------------------------
@@ -581,7 +498,7 @@ void check_error(char const* name, boost::redis::error ev)
static_cast<std::underlying_type<boost::redis::error>::type>(ev)));
}
BOOST_AUTO_TEST_CASE(error)
BOOST_AUTO_TEST_CASE(cover_error)
{
check_error("boost.redis", boost::redis::error::invalid_data_type);
check_error("boost.redis", boost::redis::error::not_a_number);
@@ -599,6 +516,12 @@ BOOST_AUTO_TEST_CASE(error)
check_error("boost.redis", boost::redis::error::not_a_double);
check_error("boost.redis", boost::redis::error::resp3_null);
check_error("boost.redis", boost::redis::error::not_connected);
check_error("boost.redis", boost::redis::error::resolve_timeout);
check_error("boost.redis", boost::redis::error::connect_timeout);
check_error("boost.redis", boost::redis::error::pong_timeout);
check_error("boost.redis", boost::redis::error::ssl_handshake_timeout);
check_error("boost.redis", boost::redis::error::sync_receive_push_failed);
check_error("boost.redis", boost::redis::error::incompatible_node_depth);
}
std::string get_type_as_str(boost::redis::resp3::type t)
@@ -660,7 +583,7 @@ BOOST_AUTO_TEST_CASE(adapter)
using boost::redis::adapter::boost_redis_adapt;
using resp3::type;
boost::system::error_code ec;
error_code ec;
response<std::string, int, ignore_t> resp;
@@ -675,3 +598,70 @@ BOOST_AUTO_TEST_CASE(adapter)
BOOST_TEST(!ec);
}
// TODO: This was an experiment, I will resume implementing this
// later.
BOOST_AUTO_TEST_CASE(adapter_as)
{
result<std::set<std::string>> set;
auto adapter = adapt2(set);
for (auto const& e: set_expected1a.value()) {
error_code ec;
adapter(e, ec);
}
}
BOOST_AUTO_TEST_CASE(cancel_one_1)
{
auto resp = push_e1a;
BOOST_TEST(resp.has_value());
consume_one(resp);
BOOST_TEST(resp.value().empty());
}
BOOST_AUTO_TEST_CASE(cancel_one_empty)
{
generic_response resp;
BOOST_TEST(resp.has_value());
consume_one(resp);
BOOST_TEST(resp.value().empty());
}
BOOST_AUTO_TEST_CASE(cancel_one_has_error)
{
generic_response resp = boost::redis::adapter::error{resp3::type::simple_string, {}};
BOOST_TEST(resp.has_error());
consume_one(resp);
BOOST_TEST(resp.has_error());
}
BOOST_AUTO_TEST_CASE(cancel_one_has_does_not_consume_past_the_end)
{
auto resp = push_e1a;
BOOST_TEST(resp.has_value());
resp.value().insert(
std::cend(resp.value()),
std::cbegin(push_e1a.value()),
std::cend(push_e1a.value()));
consume_one(resp);
BOOST_CHECK_EQUAL(resp.value().size(), push_e1a.value().size());
}
BOOST_AUTO_TEST_CASE(cancel_one_incompatible_depth)
{
auto resp = streamed_string_e1;
BOOST_TEST(resp.has_value());
error_code ec;
consume_one(resp, ec);
error_code expected = error::incompatible_node_depth;
BOOST_CHECK_EQUAL(ec, expected);
BOOST_CHECK_EQUAL(resp.value().size(), push_e1a.value().size());
}

View File

@@ -1,78 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/detail/read.hpp>
#include <boost/redis/detail/write.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <string>
#include <iostream>
#define BOOST_TEST_MODULE conn-tls
#include <boost/test/included/unit_test.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace redis = boost::redis;
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using boost::redis::adapter::adapt2;
using net::ip::tcp;
using boost::redis::request;
using boost::redis::adapter::result;
using redis::config;
auto co_main(config cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
resolver resv{ex};
auto const addrs = co_await resv.async_resolve(cfg.addr.host, cfg.addr.port);
tcp_socket socket{ex};
co_await net::async_connect(socket, addrs);
// Creates the request and writes to the socket.
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
co_await redis::detail::async_write(socket, req);
// Responses
std::string buffer;
result<std::string> resp;
std::size_t consumed = 0;
// Reads the responses to all commands in the request.
auto dbuf = net::dynamic_buffer(buffer);
consumed = co_await redis::detail::async_read(socket, dbuf);
dbuf.consume(consumed);
consumed = co_await redis::detail::async_read(socket, dbuf, adapt2(resp));
dbuf.consume(consumed);
consumed = co_await redis::detail::async_read(socket, dbuf);
dbuf.consume(consumed);
std::cout << "Ping: " << resp.value() << std::endl;
}
BOOST_AUTO_TEST_CASE(low_level_async)
{
net::io_context ioc;
net::co_spawn(ioc, std::move(co_main({})), net::detached);
ioc.run();
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
BOOST_AUTO_TEST_CASE(low_level_async)
{
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -1,61 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/read.hpp>
#include <boost/redis/detail/write.hpp>
#include <boost/redis/adapter/adapt.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#define BOOST_TEST_MODULE conn-quit
#include <boost/test/included/unit_test.hpp>
#include <string>
#include <iostream>
namespace net = boost::asio;
namespace redis = boost::redis;
using boost::redis::adapter::adapt2;
using boost::redis::request;
using boost::redis::adapter::result;
BOOST_AUTO_TEST_CASE(low_level_sync)
{
try {
std::string const host = "127.0.0.1";
std::string const port = "6379";
net::io_context ioc;
net::ip::tcp::resolver resv{ioc};
auto const res = resv.resolve(host, port);
net::ip::tcp::socket socket{ioc};
net::connect(socket, res);
// Creates the request and writes to the socket.
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
redis::detail::write(socket, req);
std::string buffer;
result<std::string> resp;
std::size_t consumed = 0;
// Reads the responses to all commands in the request.
auto dbuf = net::dynamic_buffer(buffer);
consumed = redis::detail::read(socket, dbuf);
dbuf.consume(consumed);
consumed = redis::detail::read(socket, dbuf, adapt2(resp));
dbuf.consume(consumed);
consumed = redis::detail::read(socket, dbuf);
dbuf.consume(consumed);
std::cout << "Ping: " << resp.value() << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}

377
tools/ci.py Executable file
View File

@@ -0,0 +1,377 @@
#!/usr/bin/python3
# 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 _cmake_bool(value: bool) -> str:
return 'ON' if value else 'OFF'
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', 'redis')
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(["python", "tools/boostdep/depinst/depinst.py", "--include", "example", "redis"])
# 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,
build_shared_libs: bool = False
):
_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=redis',
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
'-DCMAKE_INSTALL_PREFIX={}'.format(_cmake_distro),
'-DBUILD_TESTING=ON',
'-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 CMake tests as a standalone project
# (BOOST_REDIS_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,
build_shared_libs: bool = False
):
_mkdir_and_cd(_boost_root.joinpath('libs', 'redis', '__build_standalone__'))
_run([
'cmake',
'-DBUILD_TESTING=ON',
'-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)),
'-DCMAKE_PREFIX_PATH={}'.format(_b2_distro),
'-DCMAKE_BUILD_TYPE={}'.format(build_type),
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
'-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', 'redis', '__build_standalone__')))
_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,
build_shared_libs: bool = False
):
test_folder = _boost_root.joinpath('libs', 'redis', '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),
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
'-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,
build_shared_libs: bool = False
):
_mkdir_and_cd(_boost_root.joinpath('libs', 'redis', '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),
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
'-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,
build_shared_libs: bool = False
):
_mkdir_and_cd(_boost_root.joinpath('libs', 'redis', '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),
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
'-DBUILD_TESTING=ON',
'..'
])
_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/redis/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.add_argument('--build-shared-libs', type=_str2bool, default=False)
subp.set_defaults(func=_build_cmake_distro)
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.add_argument('--build-shared-libs', type=_str2bool, default=False)
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.add_argument('--build-shared-libs', type=_str2bool, default=False)
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.add_argument('--build-shared-libs', type=_str2bool, default=False)
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.add_argument('--build-shared-libs', type=_str2bool, default=False)
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()

12
tools/user-config.jam Normal file
View File

@@ -0,0 +1,12 @@
# Used on CI. This is required on Windows to make b2 find openssl
import os ;
local OPENSSL_ROOT = [ os.environ OPENSSL_ROOT ] ;
using openssl : :
<include>$(OPENSSL_ROOT)/include
<search>$(OPENSSL_ROOT)/lib
<ssl-name>libssl
<crypto-name>libcrypto
;