Skip to content

Commit 786a1ea

Browse files
authored
Merge pull request #7 from soupglasses/feat/install-cmake
feat: allow mtest to be installed
2 parents 99d7d4c + 4abb0b7 commit 786a1ea

File tree

11 files changed

+346
-44
lines changed

11 files changed

+346
-44
lines changed

.github/workflows/cmake.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# MIT Licensed
2+
# Copyright (c) 2024 soupglasses
3+
# Copyright (c) 2020 GitHub
4+
# See original: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml
5+
6+
name: CMake
7+
8+
on:
9+
push:
10+
branches: [ $default-branch ]
11+
pull_request:
12+
branches: [ $default-branch ]
13+
14+
jobs:
15+
build:
16+
runs-on: ${{ matrix.os }}
17+
18+
strategy:
19+
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
20+
fail-fast: false
21+
22+
# Set up a matrix to run the following 2 configurations:
23+
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
24+
# 3. <Linux, Release, latest Clang compiler toolchain on the default runner image, default generator>
25+
#
26+
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
27+
matrix:
28+
os: [ubuntu-latest]
29+
build_type: [Release]
30+
c_compiler: [gcc, clang]
31+
include:
32+
- os: ubuntu-latest
33+
c_compiler: gcc
34+
- os: ubuntu-latest
35+
c_compiler: clang
36+
37+
steps:
38+
- uses: actions/checkout@v4
39+
40+
- name: Set reusable strings
41+
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
42+
id: strings
43+
shell: bash
44+
run: |
45+
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
46+
47+
- name: Configure CMake
48+
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
49+
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
50+
run: >
51+
cmake -B ${{ steps.strings.outputs.build-output-dir }}
52+
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
53+
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
54+
-S ${{ github.workspace }}
55+
56+
- name: Build
57+
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
58+
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
59+
60+
- name: Test
61+
working-directory: ${{ steps.strings.outputs.build-output-dir }}
62+
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
63+
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
64+
run: ctest --build-config ${{ matrix.build_type }}

.github/workflows/install.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# MIT Licensed
2+
# Copyright (c) 2024 soupglasses
3+
# Copyright (c) 2020 GitHub
4+
# See original: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml
5+
6+
name: CMake Install
7+
8+
on:
9+
push:
10+
branches: [ $default-branch ]
11+
pull_request:
12+
branches: [ $default-branch ]
13+
14+
jobs:
15+
build:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: FetchContent Test
22+
run: |
23+
set -x
24+
mkdir ../fetchcontent_mtest
25+
cp example/my_test.c ../fetchcontent_mtest/my_test.c
26+
cd ../fetchcontent_mtest
27+
cat <<EOF > CMakeLists.txt
28+
cmake_minimum_required(VERSION 3.14)
29+
project(mtest_example_fetchcontent LANGUAGES C)
30+
include(FetchContent)
31+
FetchContent_Declare(mtest SOURCE_DIR "\${CMAKE_CURRENT_LIST_DIR}/../mtest/")
32+
FetchContent_MakeAvailable(mtest)
33+
enable_testing()
34+
add_executable(my_example_test my_test.c)
35+
target_link_libraries(my_example_test mtest)
36+
discover_tests(my_example_test)
37+
EOF
38+
cat CMakeLists.txt
39+
cmake -B build -DCMAKE_BUILD_TYPE=Release -S .
40+
cmake --build build
41+
ls build
42+
ctest --test-dir build
43+
44+
- name: Git Submodule Test
45+
run: |
46+
set -x
47+
mkdir ../git_submodule_mtest
48+
cp example/my_test.c ../git_submodule_mtest/my_test.c
49+
cd ../git_submodule_mtest
50+
git config --global protocol.file.allow always
51+
git config --global init.defaultBranch main
52+
git config --global user.email "[email protected]"
53+
git config --global user.name "Your Name"
54+
git init
55+
mkdir extern
56+
git submodule add ../mtest extern/mtest
57+
git commit -m "init mtest dependency"
58+
cat <<EOF > CMakeLists.txt
59+
cmake_minimum_required(VERSION 3.14)
60+
project(mtest_example_fetchcontent LANGUAGES C)
61+
add_subdirectory(extern/mtest)
62+
enable_testing()
63+
add_executable(my_example_test my_test.c)
64+
target_link_libraries(my_example_test mtest)
65+
discover_tests(my_example_test)
66+
EOF
67+
cat CMakeLists.txt
68+
cmake -B build -DCMAKE_BUILD_TYPE=Release -S .
69+
cmake --build build
70+
ctest --test-dir build
71+
72+
- name: Install Test
73+
run: |
74+
set -x
75+
cmake -B ./build -DCMAKE_BUILD_TYPE=Release -S .
76+
cmake --build ./build
77+
sudo cmake --install ./build
78+
mkdir ../install_mtest
79+
cp example/my_test.c ../install_mtest/my_test.c
80+
cd ../install_mtest
81+
cat <<EOF > CMakeLists.txt
82+
cmake_minimum_required(VERSION 3.14)
83+
project(mtest_example_install LANGUAGES C)
84+
find_package(mtest REQUIRED)
85+
enable_testing()
86+
add_executable(my_example_test my_test.c)
87+
target_link_libraries(my_example_test mtest)
88+
discover_tests(my_example_test)
89+
EOF
90+
cat CMakeLists.txt
91+
cmake -B build -DCMAKE_BUILD_TYPE=Release -S .
92+
cmake --build build
93+
ctest --test-dir build

CMakeLists.txt

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,37 @@ cmake_minimum_required(VERSION 3.14)
22
project(mtest VERSION 0.1.1 LANGUAGES C)
33

44
# Determine if mtest is built as a subproject (using add_subdirectory) or if it is the main project.
5-
set(MAIN_PROJECT OFF)
5+
set(MTEST_IS_MAIN_PROJECT OFF)
66
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
7-
set(MAIN_PROJECT ON)
7+
set(MTEST_IS_MAIN_PROJECT ON)
88
endif()
99

10-
add_library(mtest INTERFACE) # Define mtest as an interface target (since it is header-only).
11-
target_include_directories(mtest INTERFACE include) # Here we define the include directory (containing "mtest.h"), and
12-
target_link_libraries(mtest INTERFACE m) # we make sure that projects using mtest will link with the C math library.
10+
# Define mtest as an interface target (since it is header-only).
11+
add_library(mtest INTERFACE)
1312

14-
# Include the definition of the discover_tests function.
15-
include(cmake/mtest.cmake)
13+
# Define the include directory (containing "mtest.h").
14+
target_include_directories(
15+
mtest INTERFACE
16+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
17+
$<INSTALL_INTERFACE:include>
18+
)
1619

17-
# Configure the example test if this is the main project.
18-
if(${MAIN_PROJECT})
20+
# Make sure that mtest will be linked together with the C math library.
21+
target_link_libraries(mtest INTERFACE m)
22+
23+
# Include the CMake script to get the discover_tests function.
24+
include(cmake/mtestDiscoverTests.cmake)
25+
26+
# Configure the example and tests if this is the main project (and not built as
27+
# a subproject using for example add_subdirectory).
28+
if(MTEST_IS_MAIN_PROJECT)
29+
# Add the needed install configuration to allow mtest to be installed as a
30+
# system wide library.
31+
include(cmake/mtestInstall.cmake)
32+
33+
# Enable testing in CMake and include our /example and /test subdirectories
34+
# which hold the internal mtest tests to validate mtest functions as
35+
# expected.
1936
enable_testing()
2037
add_subdirectory(example)
2138
add_subdirectory(test)

README.md

Lines changed: 109 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,69 @@
1-
# mtest
1+
# mtest
22
A minimalist C unit testing framework
33
- A single header-file with plain C and `#define` macros.
44
- Plays well with CTest with a `discover_tests()` CMake function.
55

66
**mtest** is a simple, C-only unit testing framework inspired by [CuTest](https://cutest.sourceforge.net/) with some modern features inspired by [doctest](https://github.com/doctest/doctest).
77

8-
For mature and feature-complete C/C++ testing frameworks, take a look at [Boost.Test](https://github.com/boostorg/test), [Catch2](https://github.com/catchorg/Catch2), [doctest](https://github.com/doctest/doctest), or [Google Test](https://github.com/google/googletest).
8+
For more mature and feature-complete C/C++ testing frameworks, take a look at [Boost.Test](https://github.com/boostorg/test), [Catch2](https://github.com/catchorg/Catch2), [doctest](https://github.com/doctest/doctest), or [Google Test](https://github.com/google/googletest).
9+
10+
**Table of Contents**
11+
- [Usage](#usage)
12+
- [Installation](#installation)
13+
- [Using CMake's `FetchContent` (Recommended)](#using-cmakes-fetchcontent-recommended)
14+
- [Using Git external submodules](#using-git-external-submodules)
15+
- [Using system-installed `mtest` (Advanced)](#using-system-installed-mtest-advanced)
16+
17+
## Usage
18+
19+
Ensure the CMake library target `mtest` is available, see the below [Installation section](#installation).
20+
21+
Once you have installed `mtest`, add something like the following code to your [CMakeLists.txt](example/CMakeLists.txt).
22+
```cmake
23+
# Put `FetchContent_MakeAvailable(mtest ...)`, `add_subdirectory(mtest)` or `find_package(mtest)` here.
24+
25+
add_executable(my_example_test my_test.c)
26+
target_link_libraries(my_example_test mtest)
27+
28+
# Automatically discover test cases in my_example_test and add them to CTest.
29+
discover_tests(my_example_test)
30+
```
31+
32+
Where the example test file [`my_test.c`](example/my_test.c) looks something like the following:
33+
```c
34+
#include "mtest.h"
35+
36+
TEST_CASE(my_first_test_case, {
37+
int i = 42;
38+
CHECK_EQ_INT(42, i); // A simple check, similar to asking if `42 == i`.
39+
})
40+
41+
TEST_CASE(my_other_test_case, {
42+
double i = 3.14;
43+
CHECK_EQ_DOUBLE(3.14, i, 0.01); // When comparing doubles, we need to specify a tolerance - here we choose 0.01
44+
})
45+
46+
TEST_CASE(my_dependent_test_case, {
47+
int i = 0;
48+
// REQUIRE checks will stop executing a test case early if it fails.
49+
// This is helpful for when our test modifies a shared variable. For example:
50+
REQUIRE_EQ_INT(0, i++);
51+
// Now the following check would only make sense if the above condition succeeded.
52+
// Therefore, we say the above test is required, since otherwise our check wouldn't
53+
// give us any meaningful output.
54+
CHECK_EQ_INT(1, i);
55+
})
56+
57+
// This macro creates a main function that can call each test case, and it tells CTest which test cases are available.
58+
MAIN_RUN_TESTS(my_first_test_case, my_other_test_case, my_dependent_test_case)
59+
```
60+
61+
See [`mtest.h`](include/mtest.h) for all test macros.
62+
63+
## Installation
64+
65+
### Using CMake's `FetchContent` (Recommended)
966
10-
## Installation (CMake)
1167
Recommended installation using CMake's FetchContent to download the latest release:
1268
```cmake
1369
include(FetchContent)
@@ -17,7 +73,9 @@ FetchContent_Declare(mtest
1773
)
1874
FetchContent_MakeAvailable(mtest)
1975
```
20-
or (alternatively) specify the Git repository and version to download:
76+
77+
Or alternatively you can specify the Git repository and version to download:
78+
2179
```cmake
2280
include(FetchContent)
2381
FetchContent_Declare(mtest
@@ -27,35 +85,58 @@ FetchContent_Declare(mtest
2785
FetchContent_MakeAvailable(mtest)
2886
```
2987

30-
Now the CMake library target `mtest` is available. For [example](example/CMakeLists.txt):
31-
```cmake
32-
add_executable(my_example_test my_test.c)
33-
target_link_libraries(my_example_test mtest)
88+
Updates can be done by changing the `URL` and `URL_HASH` in the first approach,
89+
or by changing the `GIT_TAG` in the second approach.
3490

35-
# Automatically discover test cases in my_example_test and add them to CTest.
36-
discover_tests(my_example_test)
91+
### Using Git external submodules
92+
93+
Lets you manage mtest as a linked directory managed by git. This is the
94+
more traditional way of linking dependencies.
95+
96+
Open a terminal and ensure you are inside your project's root
97+
(where your main CMakeLists.txt file is located). Then type the following:
98+
```bash
99+
mkdir extern
100+
git submodule add https://github.com/MortenSchou/mtest.git extern/mtest
101+
git commit -m "add mtest dependency"
37102
```
38103

39-
## Usage
40-
Example test file [`my_test.c`](example/my_test.c):
41-
```C
42-
#include "mtest.h"
104+
Now you can add the following to your CMakeLists.txt:
105+
```cmake
106+
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/mtest/CMakeLists.txt")
107+
message(FATAL_ERROR "The git submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Did you forget to run `git submodule init` after cloning?")
108+
else()
109+
add_subdirectory(extern/mtest)
110+
endif()
111+
```
43112

44-
TEST_CASE(my_first_test_case, {
45-
int i = 42;
46-
CHECK_EQ_INT(1, i); // Test fails, but we continue running.
47-
REQUIRE_GT_INT(i, 0); // This test is executed and succeeds, but the test case failed due to the previous line.
48-
})
113+
Updating mtest can be done with the `git submodule update` command, then
114+
using git to commit the updated submodule directory.
49115

50-
TEST_CASE(my_other_test_case, {
51-
double i = 3.14;
52-
// When comparing doubles, we need to specify a tolerance - here we choose 0.01
53-
REQUIRE_EQ_DOUBLE(4.0, i, 0.01); // Test fails and the test case exits.
54-
CHECK_TRUE(1); // Not executed but it would succeed.
55-
})
116+
### Using system-installed `mtest` (Advanced)
56117

57-
// This macro creates a main function that can call each test case, and it tells CTest which test cases are available.
58-
MAIN_RUN_TESTS(my_first_test_case, my_other_test_case)
118+
This is likely not what you want, and it is recommended you pick one of the
119+
other approaches, as this does not ensure that anyone using your project will
120+
have the same mtest library installed as you, or even that its installed.
121+
122+
However, this approach can be helpful if you are packaging your project, or have
123+
otherwise installed mtest as part of your operating system.
124+
125+
If you do not have mtest already installed, you can run the following two
126+
commands:
127+
```bash
128+
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
129+
sudo cmake --install build
130+
```
131+
132+
Then you can add the following to your primary CMakeLists.txt:
133+
```cmake
134+
find_package(mtest REQUIRED)
59135
```
60136

61-
See [`mtest.h`](include/mtest.h) for more test macros.
137+
To update when using `cmake --install`, you need to `git pull` this project,
138+
and rerun the above cmake commands.
139+
140+
To uninstall the `cmake --install` approach, you need to go manually
141+
delete each file as listed in the generated `cmake_build/install_manifest.txt`
142+
file.

cmake/mtestConfig.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include(${CMAKE_CURRENT_LIST_DIR}/mtestTargets.cmake)
2+
include(${CMAKE_CURRENT_LIST_DIR}/mtestDiscoverTests.cmake)

cmake/mtest.cmake renamed to cmake/mtestDiscoverTests.cmake

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ function(discover_tests)
1010
-D "TEST_TARGET=${TARGET}"
1111
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
1212
-D "CTEST_FILE=${ctest_tests_file}"
13-
-P "${_MTEST_DISCOVER_TEST_SCRIPT}"
13+
-P "${_MTEST_GENERATE_TEST_SCRIPT}"
1414
VERBATIM
1515
)
1616
set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_tests_file}")
1717
endforeach()
1818
endfunction()
1919

20-
# Specify location of the mtest_add_tests.cmake script relative to current location (not relative to where discover_tests() is called).
21-
if(${MAIN_PROJECT})
22-
set(_MTEST_DISCOVER_TEST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/mtest_add_tests.cmake)
20+
# Specify location of the mtestGenerateCTestFile.cmake script relative to current location (not relative to where discover_tests() is called).
21+
if(DEFINED MTEST_IS_MAIN_PROJECT AND NOT MTEST_IS_MAIN_PROJECT)
22+
set(_MTEST_GENERATE_TEST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/mtestGenerateCTestFile.cmake PARENT_SCOPE)
2323
else ()
24-
set(_MTEST_DISCOVER_TEST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/mtest_add_tests.cmake PARENT_SCOPE)
24+
set(_MTEST_GENERATE_TEST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/mtestGenerateCTestFile.cmake)
2525
endif ()
File renamed without changes.

0 commit comments

Comments
 (0)