Skip to content

Commit 18b9cfd

Browse files
authored
ci: basic add_subdirectory cmake integration test (#447)
This is the first in a series of PRs that adds cmake integration tests to CI. The purpose of these tests is to validate that a test application can incorporate the LaunchDarkly SDKs via cmake. The tests are organized by incorporation method. This first test is for `add_subdirectory`, which is currently the only supported way of using the SDK.
1 parent 8c255b6 commit 18b9cfd

File tree

11 files changed

+359
-6
lines changed

11 files changed

+359
-6
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# This is a composite to allow sharing these steps into other workflows.
2+
# It isn't a shared workflow, because then it isn't convenient to add
3+
# additional package-specific steps.
4+
name: CMake Integration Test
5+
description: 'CMake integration test suitable for running on multiple platforms.'
6+
inputs:
7+
platform_version:
8+
description: 'Boost platform version'
9+
required: false
10+
default: "22.04"
11+
toolset:
12+
description: 'Boost toolset'
13+
required: false
14+
15+
16+
runs:
17+
using: composite
18+
steps:
19+
- name: Install Ninja
20+
uses: ./.github/actions/install-ninja
21+
- name: Install boost
22+
uses: ./.github/actions/install-boost
23+
id: install-boost
24+
with:
25+
platform_version: ${{ inputs.platform_version }}
26+
toolset: ${{ inputs.toolset }}
27+
- name: Install OpenSSL
28+
uses: ./.github/actions/install-openssl
29+
id: install-openssl
30+
- name: Configure CMake Integration Tests
31+
shell: bash
32+
run: ./scripts/configure-cmake-integration-tests.sh
33+
env:
34+
# These will be injected into the SDK CMake project on the command line, via "-D BOOST_ROOT=..."
35+
# and "-D OPENSSL_ROOT_DIR=...". When the integration tests are configured, they will then be passed
36+
# along in the same manner to those test projects via the command line.
37+
BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }}
38+
OPENSSL_ROOT_DIR: ${{ steps.install-openssl.outputs.OPENSSL_ROOT_DIR }}
39+
- name: Run CMake Integration Tests
40+
shell: bash
41+
run: |
42+
export CTEST_OUTPUT_ON_FAILURE=1
43+
cd build/cmake-tests && ctest

.github/workflows/cmake.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: cmake-integration
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths-ignore:
7+
- '**.md' # Do not need to run CI for markdown changes.
8+
pull_request:
9+
branches: [ "main", "feat/**" ]
10+
paths-ignore:
11+
- '**.md'
12+
schedule:
13+
# Run daily at midnight PST
14+
- cron: '0 8 * * *'
15+
16+
jobs:
17+
test-ubuntu:
18+
runs-on: ubuntu-22.04
19+
steps:
20+
- uses: actions/checkout@v4
21+
- uses: ./.github/actions/cmake-test
22+
with:
23+
platform_version: '22.04'
24+
25+
test-macos:
26+
runs-on: macos-12
27+
steps:
28+
- uses: actions/checkout@v4
29+
- uses: ./.github/actions/cmake-test
30+
with:
31+
platform_version: '12'
32+
33+
test-windows:
34+
runs-on: windows-2022
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: ilammy/msvc-dev-cmd@v1
38+
- uses: ./.github/actions/cmake-test
39+
env:
40+
BOOST_ROOT: 'C:\local\boost_1_81_0\lib64-msvc-14.3'
41+
with:
42+
platform_version: 2022
43+
toolset: msvc

CMakeLists.txt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ cmake_dependent_option(LD_BUILD_CONTRACT_TESTS
6565
OFF # otherwise, off
6666
)
6767

68+
# Add an option for enabling the "CMake Integration Tests" (see cmake-tests README).
69+
# These tests require testing to be enabled (BUILD_TESTING), but aren't unit tests, so are disabled by default.
70+
cmake_dependent_option(LD_CMAKE_INTEGRATION_TESTS
71+
"Test integration of SDK into other CMake projects" OFF # Default to disabling the cmake integration tests.
72+
"BUILD_TESTING" # Only expose if testing is enabled.
73+
OFF # otherwise, off.
74+
)
75+
6876
# The general strategy is to produce a fat artifact containing all of our dependencies so users
6977
# only have a single thing to link. We should support this either being a static or shared library.
7078
# Because OpenSSL is a large, and security relevant dependency, we should have a separate option
@@ -108,6 +116,13 @@ if (LD_BUILD_UNIT_TESTS)
108116
enable_testing()
109117
endif ()
110118

119+
if (LD_CMAKE_INTEGRATION_TESTS)
120+
message(STATUS "LaunchDarkly: building CMake integration tests")
121+
add_subdirectory(cmake-tests)
122+
enable_testing()
123+
endif ()
124+
125+
111126
if (LD_DYNAMIC_LINK_OPENSSL)
112127
message(STATUS "LaunchDarkly: searching for shared OpenSSL library")
113128
set(OPENSSL_USE_STATIC_LIBS OFF)
@@ -130,13 +145,13 @@ endif ()
130145

131146
if (LD_BUILD_SHARED_LIBS)
132147
if (LD_BUILD_EXPORT_ALL_SYMBOLS)
133-
message(STATUS "LaunchDarkly: exposing all symbols in shared libraries")
148+
message(STATUS "LaunchDarkly: exposing all symbols in shared libraries")
134149
else ()
135-
message(STATUS "LaunchDarkly: hiding all symbols in shared libraries except for C API")
136-
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
137-
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
138-
endif()
139-
endif()
150+
message(STATUS "LaunchDarkly: hiding all symbols in shared libraries except for C API")
151+
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
152+
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
153+
endif ()
154+
endif ()
140155

141156
set(Boost_USE_MULTITHREADED ON)
142157
set(Boost_USE_STATIC_RUNTIME OFF)

cmake-tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include(declareProjectTest.cmake)
2+
add_subdirectory(test_add_subdirectory)

cmake-tests/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
## CMake project tests overview
2+
3+
This directory contains tests for various integration techniques that users of the
4+
LaunchDarkly C++ SDKs may employ.
5+
6+
Each test takes the form of a minimal CMake project with a `CMakeLists.txt` and `main.cpp`.
7+
An additional `CMakeLists.txt` sets up the test properties.
8+
9+
Structure:
10+
11+
```
12+
some_test_directory
13+
project # Contains the CMake project under test.
14+
- main.cpp # Minimal code that invokes LaunchDarkly SDK.
15+
- CMakeLists.txt # CMake configuration that builds the project executable.
16+
- CMakeLists.txt # CMake configuration that sets up the CTest machinery for this test.
17+
```
18+
19+
*Important note about `main.cpp`*:
20+
21+
The optimizer employed by whatever toolchain is building the project might omit function definitions in the SDK
22+
during static linking, if those functions are proven to be unused.
23+
24+
The code in the main file should not have any branches that allow this to happen
25+
(such as a check for an SDK key, like in the hello demo projects.)
26+
27+
This could obscure linker errors that would have otherwise been caught.
28+
29+
## CMake test setup
30+
31+
The toplevel `CMakeLists.txt` in each subdirectory is responsible for setting up
32+
the actual CTest tests that configure and build the projects.
33+
34+
Note, the logic described below is encapsulated in two macros defined in `declareProjectTest.cmake`, so that
35+
that new tests don't need to copy boilerplate.
36+
37+
Test creation is generally done in two phases:
38+
39+
1) Make a test that configures the project (simulating `cmake .. [options]`)
40+
2) Make a test that builds the project (simulating `cmake --build .`)
41+
42+
The tests are ordered via `set_tests_properties` to ensure the configure test
43+
runs before the build test, as would be expected.
44+
45+
The test creation logic harbors additional complexity because these tests are executed
46+
in CI on multiple types of executors (Windows/Mac/Linux) in various configurations.
47+
48+
In particular, some environment variables must be forwarded to each test project CMake configuration.
49+
These include `C` and `CXX` variables, which are explicitly set/overridden in the `clang11` CI build.
50+
Without setting these, the test would fail to build with the same compilers as the SDK.
51+
52+
Additionally, certain variables must be forwarded to each test project CMake configuration.
53+
54+
| Variable | Explanation |
55+
|--------------------|------------------|
56+
| `BOOST_ROOT` | Path to Boost. |
57+
| `OPENSSL_ROOT_DIR` | Path to OpenSSL. |
58+
59+
## Tests
60+
61+
### cmake_projects/test_add_subdirectory
62+
63+
Checks that a project can include the SDK as a sub-project, via `add_subdirectory`.
64+
This would be a likely use-case when the repo is a submodule of another project.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# This file defines macros which can be used to setup
2+
# new cmake project tests without introducing excessive boilerplate.
3+
4+
# declare_add_subdirectory_test(<name of test>):
5+
# Use when the test depends on launchdarkly via add_subdirectory.
6+
7+
# declare_find_package_test(<test name>):
8+
# Use when the test depends on launchdarkly via find_package.
9+
10+
# add_build_step(<test name>):
11+
# By default, the declare_* macros result in a test where "cmake -DSOMEVARIABLE=WHATEVER .."
12+
# (the cmake configure step) is invoked. This may be sufficient for a particular test,
13+
# for example testing that the configure step fails.
14+
# If the test should also invoke "cmake --build .", use this macro.
15+
16+
# require_configure_failure(<test name>):
17+
# Asserts that the cmake configure step should fail. For example, this would
18+
# happen if a required version of a dependency couldn't be satisfied with find_package.
19+
20+
# require_build_failure(<test name>):
21+
# Asserts that the cmake build step should fail.
22+
23+
macro(declare_add_subdirectory_test name)
24+
set(test_prefix ${name})
25+
26+
add_test(
27+
NAME ${test_prefix}_configure
28+
COMMAND
29+
${CMAKE_COMMAND}
30+
# Since project/CMakeLists.txt is going to call add_subdirectory(), it needs to know where
31+
# the SDK's project is (which is actually a couple directories above this particular file; not normally the case.)
32+
# The variable name is arbitrary.
33+
-DLAUNCHDARKLY_SOURCE_DIR=${PROJECT_SOURCE_DIR}
34+
# Do not setup all of the SDK's testing machinery, which would normally happen when calling add_subdirectory.
35+
-DBUILD_TESTING=OFF
36+
-DBOOST_ROOT=${BOOST_ROOT}
37+
-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}
38+
${CMAKE_CURRENT_SOURCE_DIR}/project
39+
)
40+
41+
set_tests_properties(${test_prefix}_configure
42+
PROPERTIES
43+
FIXTURES_SETUP ${test_prefix}
44+
# Forward along the CC and CXX environment variables, because clang11 CI build uses them.
45+
ENVIRONMENT "CC=${CMAKE_C_COMPILER};CXX=${CMAKE_CXX_COMPILER}"
46+
)
47+
endmacro()
48+
49+
macro(require_configure_failure name)
50+
set_tests_properties(${name}_configure PROPERTIES WILL_FAIL TRUE)
51+
endmacro()
52+
53+
macro(require_build_failure name)
54+
set_tests_properties(${name}_build PROPERTIES WILL_FAIL TRUE)
55+
endmacro()
56+
57+
macro(add_build_step name)
58+
# Setup a 'test' to perform the cmake build step.
59+
add_test(
60+
NAME ${name}_build
61+
COMMAND ${CMAKE_COMMAND} --build .
62+
)
63+
64+
set_tests_properties(${name}_build
65+
PROPERTIES
66+
FIXTURES_REQUIRED ${name}
67+
)
68+
endmacro()
69+
70+
macro(declare_find_package_test name)
71+
# This test assumes that the SDK has been installed at CMAKE_INSTALL_PREFIX.
72+
set(test_prefix ${name})
73+
74+
add_test(
75+
NAME ${test_prefix}_configure
76+
COMMAND
77+
${CMAKE_COMMAND}
78+
# Since project/CMakeLists.txt uses find_package(), it needs to know where to find
79+
# ldserverapiConfig.cmake. That can be found where the SDK is installed, which is CMAKE_INSTALL_PREFIX.
80+
-DCMAKE_PREFIX_PATH=${CMAKE_INSTALL_PREFIX}
81+
${CMAKE_CURRENT_SOURCE_DIR}/project
82+
)
83+
84+
set_tests_properties(${test_prefix}_configure
85+
PROPERTIES
86+
FIXTURES_SETUP ${test_prefix}
87+
# Forward along the CC and CXX environment variables, because clang11 CI build uses them.
88+
ENVIRONMENT "CC=${CMAKE_C_COMPILER};CXX=${CMAKE_CXX_COMPILER}"
89+
)
90+
endmacro()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
declare_add_subdirectory_test(test_add_subdirectory)
2+
add_build_step(test_add_subdirectory)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
cmake_minimum_required(VERSION 3.19)
2+
3+
project(AddSubdirectoryTest)
4+
5+
add_subdirectory(
6+
# Source directory where the SDK's CMakeLists.txt is located.
7+
${LAUNCHDARKLY_SOURCE_DIR}
8+
# Binary directory must be specified when using an out-of-tree source.
9+
${CMAKE_CURRENT_BINARY_DIR}/launchdarkly
10+
)
11+
12+
set(TARGET_PREFIX add_subdirectory)
13+
14+
# Server-side
15+
add_executable(${TARGET_PREFIX}_server_cpp main_server.cpp)
16+
target_link_libraries(${TARGET_PREFIX}_server_cpp launchdarkly-cpp-server)
17+
18+
add_executable(${TARGET_PREFIX}_server_cpp_alias main_server.cpp)
19+
target_link_libraries(${TARGET_PREFIX}_server_cpp_alias launchdarkly::server)
20+
21+
# Client-side
22+
add_executable(${TARGET_PREFIX}_client_cpp main_client.cpp)
23+
target_link_libraries(${TARGET_PREFIX}_client_cpp launchdarkly-cpp-client)
24+
25+
add_executable(${TARGET_PREFIX}_client_cpp_alias main_client.cpp)
26+
target_link_libraries(${TARGET_PREFIX}_client_cpp_alias launchdarkly::client)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <launchdarkly/client_side/client.hpp>
2+
#include <launchdarkly/context_builder.hpp>
3+
4+
#include <cstring>
5+
#include <iostream>
6+
7+
using namespace launchdarkly;
8+
using namespace launchdarkly::client_side;
9+
10+
int main() {
11+
auto config = ConfigBuilder("sdk-key").Build();
12+
if (!config) {
13+
std::cout << "error: config is invalid: " << config.error() << '\n';
14+
return 1;
15+
}
16+
17+
auto context =
18+
ContextBuilder().Kind("user", "example-user-key").Name("Sandy").Build();
19+
20+
auto client = Client(std::move(*config), std::move(context));
21+
22+
client.StartAsync();
23+
24+
std::cout << client.Initialized() << '\n';
25+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <launchdarkly/server_side/client.hpp>
2+
#include <launchdarkly/server_side/config/config_builder.hpp>
3+
4+
#include <cstring>
5+
#include <iostream>
6+
7+
using namespace launchdarkly;
8+
using namespace launchdarkly::server_side;
9+
10+
int main() {
11+
auto config = ConfigBuilder("sdk-key").Build();
12+
if (!config) {
13+
std::cout << "error: config is invalid: " << config.error() << '\n';
14+
return 1;
15+
}
16+
17+
auto client = Client(std::move(*config));
18+
19+
client.StartAsync();
20+
21+
std::cout << client.Initialized() << '\n';
22+
23+
}

0 commit comments

Comments
 (0)