Inital commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
[*]
|
||||
end_of_line = LF
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{yml,json,lua,md,html}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# CMake
|
||||
build
|
||||
build-*
|
||||
cmake-build-*
|
||||
|
||||
# Editors
|
||||
.vscode
|
||||
.idea
|
||||
.ccls
|
||||
.ccls-cache
|
||||
.cache
|
||||
compile_commands.json
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -0,0 +1,135 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project Overview
|
||||
|
||||
`cuber` is an OpenGL 3D renderer with multiple scenes.
|
||||
|
||||
## Build System
|
||||
|
||||
- **Generator:** Ninja
|
||||
- **CMake minimum:** 3.21
|
||||
- **C standard:** C23
|
||||
|
||||
### Commands
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build # build
|
||||
ninja -C build test # run tests
|
||||
./build/main # run (Linux/macOS)
|
||||
./build/main.exe # run (Windows)
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
Dependencies are managed via custom `Find*.cmake` scripts in `deps/`.
|
||||
These scripts use `FetchContent` under the hood to download and build
|
||||
libraries automatically.
|
||||
|
||||
To add a new dependency:
|
||||
|
||||
1. Add the corresponding `Find<name>.cmake` to `deps/`
|
||||
2. Add `find_package(<name> REQUIRED)` to `CMakeLists.txt`
|
||||
3. Link with `<name>::<name>` in `target_link_libraries()`
|
||||
|
||||
### Static Libraries
|
||||
|
||||
The project is split into static libraries:
|
||||
|
||||
- **`cbt_opengl`** — OpenGL abstraction and window management (window,
|
||||
context, buffer, texture, vao, shader, descriptor)
|
||||
- **`cbt_scene`** — Base scene class
|
||||
- **`scenes_cube`** — Spinning cube scene implementation
|
||||
- **`scenes_sphere`** — Cube-to-sphere mapped mesh with diffuse lighting
|
||||
|
||||
### CMake Module Path
|
||||
|
||||
`deps/` is added to `CMAKE_MODULE_PATH` so `find_package()` resolves
|
||||
to the custom scripts instead of system-installed packages.
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
- **Language:** C23
|
||||
- **Trailing return type** for function signatures (e.g. `auto fn() -> void`)
|
||||
- **4-space indentation**
|
||||
- **No semicolons after closing braces** for namespaces/classes
|
||||
- `auto` for obvious types (e.g. `auto main(...) -> int`)
|
||||
- **East const** (e.g. `char const*` not `const char*`)
|
||||
- **Trailing return type** for all function definitions, including
|
||||
operators (e.g. `auto operator=(T&&) noexcept -> T&`)
|
||||
- **Public members first** in class declarations, private members at the
|
||||
bottom
|
||||
- `<>` includes only for system headers (std, OS, etc.)
|
||||
- `""` includes for third-party dependencies (e.g. `fmt`,
|
||||
`nlohmann/json`)
|
||||
- **Naming:** `snake_case` for variables, functions, and classes
|
||||
- **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants
|
||||
- Include order:
|
||||
1. C++ standard library headers (`<chrono>`, `<vector>`, etc.)
|
||||
2. *(blank line)*
|
||||
3. C standard library headers (`<stdlib.h>`, `<string.h>`, etc.)
|
||||
4. *(blank line)*
|
||||
5. OS-specific headers (Windows API, POSIX, etc.)
|
||||
6. *(blank line)*
|
||||
7. Third-party dependencies (`"fmt/core.h"`, etc.)
|
||||
8. *(blank line)*
|
||||
9. Local/project headers
|
||||
|
||||
## Shell Scripts
|
||||
|
||||
- Always use `#!/bin/sh` shebang for shell scripts
|
||||
- Scripts must be POSIX compliant (no bashisms)
|
||||
- When providing commands to users:
|
||||
- Windows/PowerShell: use `` ` `` for line continuation
|
||||
- Unix/Linux/macOS: use `\` for line continuation
|
||||
|
||||
## Commit Messages
|
||||
|
||||
- Follow the 50/72 rule:
|
||||
- Subject line: max 50 characters
|
||||
- Body lines: wrapped at 72 characters
|
||||
- Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`,
|
||||
etc.)
|
||||
- Separate subject from body with a blank line
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
feat: add stopwatch timer
|
||||
|
||||
Replace Hello World with a live stopwatch that prints elapsed time
|
||||
in HH:MM:SS.mmm format, updating every 10ms with color output.
|
||||
```
|
||||
|
||||
## Documentation (Markdown)
|
||||
|
||||
- Wrap normal text and lists at **max 80 columns** (for readability in
|
||||
terminals and editors).
|
||||
- **Exceptions**: Tables and code blocks (```` ``` ````) can exceed 80
|
||||
columns when formatting requires it (e.g. trees, alignment).
|
||||
- Use standard Markdown: `**bold**`, `` `inline code` ``, `##` headings,
|
||||
`-` or numbered lists, fenced code blocks with language hints
|
||||
(```` ```cpp ````, ```` ```sh ````).
|
||||
- Keep examples concise, up-to-date, and self-documenting.
|
||||
- This file (`AGENTS.md`) follows its own rules.
|
||||
|
||||
## Source Layout
|
||||
|
||||
```text
|
||||
ctdd/
|
||||
str.h / str.c Pure string utilities (no dependencies)
|
||||
report.h / report.c Formats a value and calls log_message()
|
||||
logger.h / logger.c Real log_message — printf to stdout
|
||||
main.c Entry point
|
||||
tests/
|
||||
test_str.c Unity state-based tests for ctdd/str
|
||||
test_report.c Interaction-based tests using CMock
|
||||
deps/
|
||||
FindUnity.cmake Fetches Unity v2.6.1 via ZIP
|
||||
FindCMock.cmake Fetches CMock v2.6.0 via ZIP
|
||||
```
|
||||
|
||||
## Platform Support
|
||||
|
||||
The project supports Windows, Linux, Emscripten, and Android via
|
||||
`Platform.cmake` and `Flags.cmake` in `deps/`.
|
||||
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(ctdd VERSION 0.1.0)
|
||||
|
||||
# CMake configuration
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps")
|
||||
|
||||
# Platform flags
|
||||
include(Platform)
|
||||
include(Flags)
|
||||
|
||||
# Example Library
|
||||
add_subdirectory(ctdd)
|
||||
|
||||
# Main executable
|
||||
add_executable(main main.c)
|
||||
target_include_directories(main PRIVATE .)
|
||||
target_compile_features(main PRIVATE c_std_23)
|
||||
target_link_libraries(main PRIVATE ctdd_str ctdd_report ctdd_logger)
|
||||
|
||||
# Testing
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
|
||||
# IDE configuration
|
||||
include(IDE)
|
||||
@@ -0,0 +1,125 @@
|
||||
# ctdd
|
||||
|
||||
A C23 project wired for test-driven development using
|
||||
[Unity](https://github.com/ThrowTheSwitch/Unity) and
|
||||
[CMock](https://github.com/ThrowTheSwitch/CMock).
|
||||
|
||||
All dependencies are fetched automatically via CMake `FetchContent` — no
|
||||
manual installation required beyond the tools listed below.
|
||||
|
||||
## Requirements
|
||||
|
||||
| Tool | Purpose |
|
||||
| ------------------- | --------------------- |
|
||||
| CMake ≥ 3.21 | Build system |
|
||||
| Ninja | Build backend |
|
||||
| C23 compiler | GCC 14+, Clang 18+ |
|
||||
| Ruby ≥ 3.0 | CMock mock generation |
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -G Ninja
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```sh
|
||||
ninja -C build test # summary output
|
||||
ctest --test-dir build -V # verbose (shows each test name)
|
||||
```
|
||||
|
||||
Run a single suite directly for the clearest output:
|
||||
|
||||
```sh
|
||||
./build/tests/test_str.exe
|
||||
./build/tests/test_report.exe
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
./build/main.exe # Windows
|
||||
./build/main # Linux / macOS
|
||||
```
|
||||
|
||||
|
||||
## Adding a new module (TDD workflow)
|
||||
|
||||
### 1. Write the header
|
||||
|
||||
```c
|
||||
// ctdd/counter.h
|
||||
#pragma once
|
||||
int counter_increment(int value);
|
||||
```
|
||||
|
||||
### 2. Write a failing test
|
||||
|
||||
```c
|
||||
// tests/test_counter.c
|
||||
#include "unity.h"
|
||||
#include "ctdd/counter.h"
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
void test_increment_adds_one(void) {
|
||||
TEST_ASSERT_EQUAL_INT(2, counter_increment(1));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_increment_adds_one);
|
||||
return UNITY_END();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Register the test in `tests/CMakeLists.txt`
|
||||
|
||||
```cmake
|
||||
add_executable(test_counter test_counter.c)
|
||||
target_include_directories(test_counter PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_counter PRIVATE ctdd_counter Unity::Unity)
|
||||
target_compile_features(test_counter PRIVATE c_std_23)
|
||||
add_test(NAME test_counter COMMAND test_counter)
|
||||
```
|
||||
|
||||
### 4. Add a stub, confirm RED, then implement GREEN
|
||||
|
||||
Stub `ctdd/counter.c` with `return 0;`, run tests to see the failure,
|
||||
then implement the real logic and confirm they pass.
|
||||
|
||||
## Mocking a dependency
|
||||
|
||||
If your module calls an external function (e.g. `log_message`), generate
|
||||
a mock from its header and add it to the test target:
|
||||
|
||||
```cmake
|
||||
# tests/CMakeLists.txt
|
||||
add_executable(test_mymodule test_mymodule.c)
|
||||
target_link_libraries(test_mymodule PRIVATE ctdd_mymodule Unity::Unity CMock::CMock)
|
||||
target_compile_features(test_mymodule PRIVATE c_std_23)
|
||||
cmock_generate_mock(test_mymodule "${CMAKE_SOURCE_DIR}/ctdd/logger.h")
|
||||
add_test(NAME test_mymodule COMMAND test_mymodule)
|
||||
```
|
||||
|
||||
CMock generates `Mocklogger.h` into the build directory. Include it and
|
||||
use the generated API in your test:
|
||||
|
||||
```c
|
||||
#include "Mocklogger.h"
|
||||
|
||||
void setUp(void) { Mocklogger_Init(); }
|
||||
void tearDown(void) { Mocklogger_Verify(); Mocklogger_Destroy(); }
|
||||
|
||||
void test_something_logs(void) {
|
||||
log_message_Expect("expected string");
|
||||
my_function_under_test();
|
||||
}
|
||||
```
|
||||
|
||||
The `_Expect` call records the expected argument. CMock verifies the
|
||||
actual argument matches at call time using string comparison, and
|
||||
`Mocklogger_Verify` confirms the expected number of calls were made.
|
||||
@@ -0,0 +1,13 @@
|
||||
add_library(ctdd_str STATIC str.c)
|
||||
target_include_directories(ctdd_str PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||
target_compile_features(ctdd_str PRIVATE c_std_23)
|
||||
|
||||
# Reporter — calls log_message(); symbol resolved by the final binary
|
||||
add_library(ctdd_report STATIC report.c)
|
||||
target_include_directories(ctdd_report PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||
target_compile_features(ctdd_report PRIVATE c_std_23)
|
||||
|
||||
# Real log_message implementation — linked into production binaries only
|
||||
add_library(ctdd_logger STATIC logger.c)
|
||||
target_include_directories(ctdd_logger PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||
target_compile_features(ctdd_logger PRIVATE c_std_23)
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "ctdd/logger.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void log_message(char const* msg) {
|
||||
printf("[LOG] %s\n", msg);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void log_message(char const* msg);
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "ctdd/report.h"
|
||||
#include "ctdd/logger.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void report_value(char const* label, int value) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s: %d", label, value);
|
||||
log_message(buf);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
/* Formats "label: value" and forwards it to log_message(). */
|
||||
void report_value(char const* label, int value);
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#include "ctdd/str.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
int str_starts_with(char const* s, char const* prefix) {
|
||||
return strncmp(s, prefix, strlen(prefix)) == 0;
|
||||
}
|
||||
|
||||
int str_ends_with(char const* s, char const* suffix) {
|
||||
size_t sl = strlen(s);
|
||||
size_t xl = strlen(suffix);
|
||||
if (xl > sl) return 0;
|
||||
return strcmp(s + sl - xl, suffix) == 0;
|
||||
}
|
||||
|
||||
int str_count(char const* s, char c) {
|
||||
int n = 0;
|
||||
for (; *s; s++) if (*s == c) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
void str_upper(char* dst, char const* src, size_t n) {
|
||||
size_t i;
|
||||
for (i = 0; i < n - 1 && src[i]; i++)
|
||||
dst[i] = (char)toupper((unsigned char)src[i]);
|
||||
dst[i] = '\0';
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
int str_starts_with(char const* s, char const* prefix);
|
||||
int str_ends_with(char const* s, char const* suffix);
|
||||
int str_count(char const* s, char c);
|
||||
void str_upper(char* dst, char const* src, size_t n);
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
# ==============================================================================
|
||||
# Find CMock
|
||||
# ==============================================================================
|
||||
# This module fetches the CMock mocking framework (depends on Unity).
|
||||
#
|
||||
# Targets provided:
|
||||
# CMock::CMock - The CMock library target
|
||||
#
|
||||
# Variables set:
|
||||
# CMock_FOUND - TRUE if CMock is available
|
||||
# CMock_LIBRARIES - The CMock library target (CMock::CMock)
|
||||
# CMock_INCLUDE_DIR - Include directories for CMock
|
||||
# CMock_VERSION - Version of CMock (if available)
|
||||
#
|
||||
# Generator variables (set when Ruby is found):
|
||||
# CMOCK_SCRIPT - Path to lib/cmock.rb
|
||||
# RUBY_EXECUTABLE - Path to ruby interpreter
|
||||
# ==============================================================================
|
||||
|
||||
if (DEFINED _FINDCMOCK_INCLUDED)
|
||||
return()
|
||||
endif()
|
||||
set(_FINDCMOCK_INCLUDED TRUE)
|
||||
|
||||
find_package(Unity REQUIRED)
|
||||
|
||||
if (DEFINED CMock_FIND_VERSION AND NOT CMock_FIND_VERSION STREQUAL "")
|
||||
set(CMOCK_VERSION "${CMock_FIND_VERSION}")
|
||||
else()
|
||||
set(CMOCK_VERSION "2.6.0")
|
||||
endif()
|
||||
|
||||
message(STATUS "Fetching CMock ${CMOCK_VERSION}")
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
cmock
|
||||
URL https://github.com/ThrowTheSwitch/CMock/archive/refs/tags/v${CMOCK_VERSION}.zip
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
|
||||
# CMock uses Meson — bypass its build system and compile src/cmock.c directly.
|
||||
# FetchContent_MakeAvailable cannot be used here (no CMakeLists.txt in CMock),
|
||||
# so we call FetchContent_Populate directly and opt into the old policy.
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0169 OLD)
|
||||
FetchContent_GetProperties(cmock)
|
||||
if (NOT cmock_POPULATED)
|
||||
FetchContent_Populate(cmock)
|
||||
endif()
|
||||
cmake_policy(POP)
|
||||
|
||||
# The Ruby generator expects vendor/unity/auto/type_sanitizer.rb — populate
|
||||
# it from the Unity source we already have rather than needing a git submodule
|
||||
set(_cmock_vendor_auto "${cmock_SOURCE_DIR}/vendor/unity/auto")
|
||||
if (NOT EXISTS "${_cmock_vendor_auto}/type_sanitizer.rb")
|
||||
file(MAKE_DIRECTORY "${_cmock_vendor_auto}")
|
||||
file(COPY "${unity_SOURCE_DIR}/auto/" DESTINATION "${_cmock_vendor_auto}")
|
||||
endif()
|
||||
|
||||
if (NOT TARGET cmock)
|
||||
add_library(cmock STATIC "${cmock_SOURCE_DIR}/src/cmock.c")
|
||||
target_include_directories(cmock PUBLIC
|
||||
"${cmock_SOURCE_DIR}/src"
|
||||
"${unity_SOURCE_DIR}/src"
|
||||
)
|
||||
target_link_libraries(cmock PUBLIC unity)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET CMock::CMock)
|
||||
add_library(CMock::CMock ALIAS cmock)
|
||||
endif()
|
||||
|
||||
set(CMock_FOUND TRUE)
|
||||
set(CMock_LIBRARIES CMock::CMock)
|
||||
set(CMock_VERSION "${CMOCK_VERSION}")
|
||||
set(CMock_INCLUDE_DIR "${cmock_SOURCE_DIR}/src")
|
||||
set(CMOCK_SCRIPT "${cmock_SOURCE_DIR}/lib/cmock.rb" CACHE FILEPATH "Path to CMock Ruby generator")
|
||||
|
||||
if (CMock_INCLUDE_DIR AND TARGET cmock)
|
||||
set_target_properties(cmock PROPERTIES
|
||||
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${CMock_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
find_program(RUBY_EXECUTABLE ruby)
|
||||
if (NOT RUBY_EXECUTABLE)
|
||||
message(WARNING "Ruby not found — CMock code generation unavailable")
|
||||
endif()
|
||||
|
||||
set(CMOCK_LICENSE_FILE "${cmock_SOURCE_DIR}/LICENSE.txt" CACHE FILEPATH "Path to CMock license file")
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
# ==============================================================================
|
||||
# Find Unity
|
||||
# ==============================================================================
|
||||
# This module fetches the Unity unit testing framework.
|
||||
#
|
||||
# Targets provided:
|
||||
# Unity::Unity - The Unity library target
|
||||
#
|
||||
# Variables set:
|
||||
# Unity_FOUND - TRUE if Unity is available
|
||||
# Unity_LIBRARIES - The Unity library target (Unity::Unity)
|
||||
# Unity_INCLUDE_DIR - Include directories for Unity
|
||||
# Unity_VERSION - Version of Unity (if available)
|
||||
# ==============================================================================
|
||||
|
||||
if (DEFINED _FINDUNITY_INCLUDED)
|
||||
return()
|
||||
endif()
|
||||
set(_FINDUNITY_INCLUDED TRUE)
|
||||
|
||||
if (DEFINED Unity_FIND_VERSION AND NOT Unity_FIND_VERSION STREQUAL "")
|
||||
set(UNITY_VERSION "${Unity_FIND_VERSION}")
|
||||
else()
|
||||
set(UNITY_VERSION "2.6.1")
|
||||
endif()
|
||||
|
||||
message(STATUS "Fetching Unity ${UNITY_VERSION}")
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
unity
|
||||
URL https://github.com/ThrowTheSwitch/Unity/archive/refs/tags/v${UNITY_VERSION}.zip
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(unity)
|
||||
|
||||
if (NOT TARGET Unity::Unity)
|
||||
if (TARGET unity)
|
||||
add_library(Unity::Unity ALIAS unity)
|
||||
else()
|
||||
message(FATAL_ERROR "Could not fetch Unity; no target unity or Unity::Unity available")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(Unity_FOUND TRUE)
|
||||
set(Unity_LIBRARIES Unity::Unity)
|
||||
set(Unity_VERSION "${UNITY_VERSION}")
|
||||
set(Unity_INCLUDE_DIR "${unity_SOURCE_DIR}/src")
|
||||
|
||||
if (Unity_INCLUDE_DIR AND TARGET unity)
|
||||
set_target_properties(unity PROPERTIES
|
||||
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${Unity_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(UNITY_LICENSE_FILE "${unity_SOURCE_DIR}/LICENSE.txt" CACHE FILEPATH "Path to Unity license file")
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
# ==============================================================================
|
||||
# Compiler Flags
|
||||
# ==============================================================================
|
||||
# Sets BASE_OPTIONS (warning flags) and BASE_DEFINITIONS.
|
||||
# Apply per-target via target_compile_options / target_compile_definitions
|
||||
# to avoid polluting fetched dependencies.
|
||||
#
|
||||
# Requires: Platform.cmake (for IS_CLANG_OR_GCC / IS_MSVC)
|
||||
# ==============================================================================
|
||||
|
||||
set(BASE_DEFINITIONS "")
|
||||
set(BASE_LIBRARIES "")
|
||||
|
||||
set(BASE_OPTIONS "")
|
||||
if (IS_CLANG_OR_GCC)
|
||||
set(BASE_OPTIONS
|
||||
"-Wall"
|
||||
"-Wextra"
|
||||
"-Werror"
|
||||
)
|
||||
elseif (IS_MSVC)
|
||||
set(BASE_OPTIONS
|
||||
"/W4"
|
||||
"/WX"
|
||||
"/utf-8"
|
||||
)
|
||||
endif()
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# ==============================================================================
|
||||
# IDE Integration
|
||||
# ==============================================================================
|
||||
# Groups dependency targets into folders for Visual Studio / Xcode.
|
||||
# Has no effect on the build.
|
||||
# ==============================================================================
|
||||
|
||||
function(set_target_folder target folder)
|
||||
if (TARGET ${target})
|
||||
set_target_properties(${target} PROPERTIES FOLDER ${folder})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if (CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR MATCHES "Xcode")
|
||||
set_target_folder(unity deps)
|
||||
set_target_folder(cmock deps)
|
||||
endif()
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
# ==============================================================================
|
||||
# Platform Detection
|
||||
# ==============================================================================
|
||||
# This module detects the current platform and compiler, setting IS_* variables
|
||||
# that can be used throughout the build system for conditional logic.
|
||||
#
|
||||
# Compiler flags set:
|
||||
# IS_CLANG_OR_GCC - TRUE if using Clang or GCC compiler
|
||||
# IS_MSVC - TRUE if using Microsoft Visual C++ compiler
|
||||
#
|
||||
# Platform flags set:
|
||||
# IS_WINDOWS - TRUE if building for Windows
|
||||
# IS_LINUX - TRUE if building for Linux
|
||||
# IS_MACOS - TRUE if building for macOS
|
||||
# IS_IOS - TRUE if building for iOS
|
||||
# IS_ANDROID - TRUE if building for Android
|
||||
# IS_EMSCRIPTEN - TRUE if building for WebAssembly via Emscripten
|
||||
# ==============================================================================
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Compiler Detection
|
||||
# ------------------------------------------------------------------------------
|
||||
set(IS_CLANG_OR_GCC FALSE)
|
||||
set(IS_MSVC FALSE)
|
||||
|
||||
if(MSVC)
|
||||
set(IS_MSVC TRUE)
|
||||
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU")
|
||||
set(IS_CLANG_OR_GCC TRUE)
|
||||
endif()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Platform Detection
|
||||
# ------------------------------------------------------------------------------
|
||||
set(IS_WINDOWS FALSE)
|
||||
set(IS_LINUX FALSE)
|
||||
set(IS_MACOS FALSE)
|
||||
set(IS_IOS FALSE)
|
||||
set(IS_ANDROID FALSE)
|
||||
set(IS_EMSCRIPTEN FALSE)
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
message(STATUS "Platform: Emscripten")
|
||||
set(IS_EMSCRIPTEN TRUE)
|
||||
elseif(ANDROID)
|
||||
message(STATUS "Platform: Android")
|
||||
set(IS_ANDROID TRUE)
|
||||
elseif(APPLE)
|
||||
if(IOS)
|
||||
message(STATUS "Platform: iOS")
|
||||
set(IS_IOS TRUE)
|
||||
else()
|
||||
message(STATUS "Platform: macOS")
|
||||
set(IS_MACOS TRUE)
|
||||
endif()
|
||||
elseif(WIN32)
|
||||
message(STATUS "Platform: Windows")
|
||||
set(IS_WINDOWS TRUE)
|
||||
elseif(UNIX)
|
||||
message(STATUS "Platform: Linux")
|
||||
set(IS_LINUX TRUE)
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown platform!")
|
||||
endif()
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "ctdd/str.h"
|
||||
#include "ctdd/report.h"
|
||||
#include "ctdd/logger.h"
|
||||
|
||||
int main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) {
|
||||
char const* words[] = { "hello", "world", "ctdd", "tdd" };
|
||||
int n = 4, tdd_words = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (str_ends_with(words[i], "tdd"))
|
||||
tdd_words++;
|
||||
}
|
||||
report_value("words ending in 'tdd'", tdd_words);
|
||||
|
||||
char upper[32];
|
||||
str_upper(upper, "hello, tdd", sizeof(upper));
|
||||
log_message(upper);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
find_package(Unity REQUIRED)
|
||||
find_package(CMock REQUIRED)
|
||||
|
||||
set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/mocks")
|
||||
file(MAKE_DIRECTORY "${MOCK_GEN_DIR}")
|
||||
|
||||
# Generate a CMock mock from a header and attach it to a target.
|
||||
# Usage: cmock_generate_mock(<target> <absolute-path-to-header>)
|
||||
# CMock names generated files Mock<Name>.h/.c (capital M, no separator)
|
||||
function(cmock_generate_mock target header)
|
||||
if (NOT RUBY_EXECUTABLE)
|
||||
message(FATAL_ERROR "Ruby is required for CMock generation")
|
||||
endif()
|
||||
get_filename_component(name "${header}" NAME_WE)
|
||||
get_filename_component(header_dir "${header}" DIRECTORY)
|
||||
set(mock_src "${MOCK_GEN_DIR}/Mock${name}.c")
|
||||
set(mock_hdr "${MOCK_GEN_DIR}/Mock${name}.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${mock_src}" "${mock_hdr}"
|
||||
COMMAND "${RUBY_EXECUTABLE}" "${CMOCK_SCRIPT}"
|
||||
"--mock_path=${MOCK_GEN_DIR}"
|
||||
"${header}"
|
||||
DEPENDS "${header}"
|
||||
COMMENT "CMock: generating Mock${name}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources("${target}" PRIVATE "${mock_src}")
|
||||
# MOCK_GEN_DIR for the generated header; header_dir so the generated
|
||||
# #include "logger.h" resolves without the ctdd/ prefix
|
||||
target_include_directories("${target}" PRIVATE "${MOCK_GEN_DIR}" "${header_dir}")
|
||||
endfunction()
|
||||
|
||||
# str tests — pure functions, no mock needed
|
||||
add_executable(test_str test_str.c)
|
||||
target_include_directories(test_str PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_str PRIVATE ctdd_str Unity::Unity)
|
||||
target_compile_features(test_str PRIVATE c_std_23)
|
||||
add_test(NAME test_str COMMAND test_str)
|
||||
|
||||
# report tests — CMock-generated mock for log_message
|
||||
add_executable(test_report test_report.c)
|
||||
target_include_directories(test_report PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
target_link_libraries(test_report PRIVATE ctdd_report Unity::Unity CMock::CMock)
|
||||
target_compile_features(test_report PRIVATE c_std_23)
|
||||
cmock_generate_mock(test_report "${CMAKE_SOURCE_DIR}/ctdd/logger.h")
|
||||
add_test(NAME test_report COMMAND test_report)
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "unity.h"
|
||||
#include "ctdd/report.h"
|
||||
#include "Mocklogger.h"
|
||||
|
||||
void setUp(void) { Mocklogger_Init(); }
|
||||
void tearDown(void) { Mocklogger_Verify(); Mocklogger_Destroy(); }
|
||||
|
||||
void test_report_formats_label_and_value(void) {
|
||||
log_message_Expect("count: 42");
|
||||
report_value("count", 42);
|
||||
}
|
||||
|
||||
void test_report_negative_value(void) {
|
||||
log_message_Expect("score: -5");
|
||||
report_value("score", -5);
|
||||
}
|
||||
|
||||
void test_report_zero(void) {
|
||||
log_message_Expect("total: 0");
|
||||
report_value("total", 0);
|
||||
}
|
||||
|
||||
void test_report_calls_log_exactly_once(void) {
|
||||
log_message_Expect("x: 1");
|
||||
report_value("x", 1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_report_formats_label_and_value);
|
||||
RUN_TEST(test_report_negative_value);
|
||||
RUN_TEST(test_report_zero);
|
||||
RUN_TEST(test_report_calls_log_exactly_once);
|
||||
return UNITY_END();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "unity.h"
|
||||
#include "ctdd/str.h"
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
void test_starts_with_match(void) {
|
||||
TEST_ASSERT_TRUE(str_starts_with("hello world", "hello"));
|
||||
}
|
||||
|
||||
void test_starts_with_no_match(void) {
|
||||
TEST_ASSERT_FALSE(str_starts_with("hello world", "world"));
|
||||
}
|
||||
|
||||
void test_starts_with_empty_prefix(void) {
|
||||
TEST_ASSERT_TRUE(str_starts_with("hello", ""));
|
||||
}
|
||||
|
||||
void test_ends_with_match(void) {
|
||||
TEST_ASSERT_TRUE(str_ends_with("hello world", "world"));
|
||||
}
|
||||
|
||||
void test_ends_with_no_match(void) {
|
||||
TEST_ASSERT_FALSE(str_ends_with("hello world", "hello"));
|
||||
}
|
||||
|
||||
void test_ends_with_full_string(void) {
|
||||
TEST_ASSERT_TRUE(str_ends_with("tdd", "tdd"));
|
||||
}
|
||||
|
||||
void test_count_multiple(void) {
|
||||
TEST_ASSERT_EQUAL_INT(3, str_count("banana", 'a'));
|
||||
}
|
||||
|
||||
void test_count_none(void) {
|
||||
TEST_ASSERT_EQUAL_INT(0, str_count("hello", 'z'));
|
||||
}
|
||||
|
||||
void test_count_single(void) {
|
||||
TEST_ASSERT_EQUAL_INT(1, str_count("ctdd", 'c'));
|
||||
}
|
||||
|
||||
void test_upper_lowercase(void) {
|
||||
char dst[16];
|
||||
str_upper(dst, "hello", sizeof(dst));
|
||||
TEST_ASSERT_EQUAL_STRING("HELLO", dst);
|
||||
}
|
||||
|
||||
void test_upper_mixed(void) {
|
||||
char dst[16];
|
||||
str_upper(dst, "Hello World", sizeof(dst));
|
||||
TEST_ASSERT_EQUAL_STRING("HELLO WORLD", dst);
|
||||
}
|
||||
|
||||
void test_upper_already_upper(void) {
|
||||
char dst[16];
|
||||
str_upper(dst, "TDD", sizeof(dst));
|
||||
TEST_ASSERT_EQUAL_STRING("TDD", dst);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_starts_with_match);
|
||||
RUN_TEST(test_starts_with_no_match);
|
||||
RUN_TEST(test_starts_with_empty_prefix);
|
||||
RUN_TEST(test_ends_with_match);
|
||||
RUN_TEST(test_ends_with_no_match);
|
||||
RUN_TEST(test_ends_with_full_string);
|
||||
RUN_TEST(test_count_multiple);
|
||||
RUN_TEST(test_count_none);
|
||||
RUN_TEST(test_count_single);
|
||||
RUN_TEST(test_upper_lowercase);
|
||||
RUN_TEST(test_upper_mixed);
|
||||
RUN_TEST(test_upper_already_upper);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user