This document outlines the coding and project structure conventions for C++ projects.
- C++23 is always required
- Use modern C++ features and idioms
- Prefer clang++ over g++ when available
- Use Werror - treat all warnings as errors
- Enable compiler hardening options for security and robustness
# Prefer clang++ if available
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(STATUS "Using Clang compiler")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(STATUS "Using GCC compiler")
endif()
# Enable warnings as errors
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
# Compiler hardening options
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2 -Wformat-security")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstack-protector")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fPIE")
# Linker hardening options (separate from compiler flags)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")-Werror- Treat all warnings as errors (fail build on warnings)-Wall -Wextra -Wpedantic- Enable comprehensive warning sets-Wformat=2 -Wformat-security- Warn about format string vulnerabilities-Wstack-protector- Enable stack protection warnings (compatible with both Clang and GCC)-fstack-protector- Enable stack protection (compatible with both Clang and GCC)-D_FORTIFY_SOURCE=2- Enable buffer overflow detection-fPIC -fPIE- Position independent code/executables-Wl,-z,relro -Wl,-z,now- Linker hardening (relocation read-only, immediate binding)- Note: These linker options work with both clang++ and g++ linkers
relro: Makes relocation sections read-only after loadingnow: Forces immediate binding of all symbols at load time
Use compact namespace syntax that follows the directory structure:
CRITICAL RULE: The root namespace MUST match the project directory name exactly.
For single project directories:
// ✅ Correct - root namespace matches project directory name
namespace projectname::component {
// code here
}
namespace projectname::module {
// code here
}For multiple project directories:
// ✅ Correct - namespace includes workspace and project directory name
namespace workspace::projectname1::component {
// code here
}
namespace workspace::projectname1::module {
// code here
}
namespace workspace::projectname2::handler {
// code here
}
namespace workspace::projectname2::database {
// code here
}❌ Incorrect patterns:
// ❌ Incorrect - generic root namespace
namespace app::component {
// code here
}
// ❌ Incorrect - don't include src/ or include/ in namespace
namespace projectname::src::component {
// code here
}
namespace projectname::include::module {
// code here
}
// ❌ Incorrect - don't use nested namespace syntax
namespace projectname {
namespace component {
// code here
}
}src/- Source files (.cpp)include/- Header files (.hpp)
Choose one of the following approaches:
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)#include <subdir/header.hpp>- Avoid
//comments - only use when necessary for non-self-explanatory code - Prefer self-documenting code over comments
- Documentation comments in headers only
- Use standard documentation format in header files
- Implementation files should not contain documentation comments
- Document all public functions and properties in header files
- Document class purpose and usage in header files
Document all public functions in header files with:
/**
* @brief Brief description of what the function does
* @param paramName Description of parameter
* @return Description of return value
* @throws ExceptionType When this exception is thrown
*/
void myFunction(int paramName);Document all public properties in header files with:
/**
* @brief Brief description of what the property represents
* @details Additional details if needed
*/
std::string myProperty;Document classes with purpose and usage:
/**
* @brief Brief description of class purpose
* @details Detailed description of class functionality and usage
*/
class MyClass {
// class implementation
};Every file must have a header comment block with:
/**
* @file filename.cpp
* @brief Brief description of file purpose
* @details Detailed description if needed
*
* @author paigeadelethompson
* @year [Use: date +%Y to get current year]
*/When you have a single class:
- Use single-word filename:
parser.hpp,validator.hpp - No subdirectory needed
- Example:
src/parser.cppwithinclude/parser.hpp
When you have multiple related classes:
- Create subdirectory:
src/component/,include/component/ - Each class gets its own file:
controller.hpp,service.hpp,manager.hpp - Must have at least
lib.hppandlib.cpp(even if empty) - Namespace follows pattern:
namespace PROJECTNAME::subdir - Example:
src/component/withinclude/component/controller.hpp,include/component/service.hpp - Each class uses namespace
projectname::component
Primary Rule: Use single-word filenames for all classes. Create subdirectories when you have multiple related classes OR when the class name is a compound word.
CRITICAL RULE: Compound words MUST be split into subdirectories, even for single classes.
When to use subdirectories:
- Multiple classes that would have the same single-word filename
- Related classes that need to be grouped together
- Compound word class names (even for single classes)
- Example:
component/controller.hpp,component/service.hpp,component/manager.hpp(all component-related) - Example:
window/main.hpp(compound word "mainwindow" → "window/main")
When NOT to use subdirectories:
- Single classes with unique single-word names
- Example:
parser.hpp,validator.hpp,processor.hpp(each is unique)
Subdirectory Requirements:
- Each subdirectory must have
lib.hppandlib.cpp(even if empty) - Namespace follows pattern:
namespace PROJECTNAME::subdir - Example:
namespace projectname::componentforcomponent/files - Example:
namespace projectname::windowforwindow/files
File naming examples:
- ✅
parser.hpp(single class, single word, no subdirectory needed) - ✅
validator.hpp(single class, single word, no subdirectory needed) - ✅
component/controller.hpp(multiple component classes, subdirectory needed) - ✅
window/main.hpp(compound word "mainwindow" → subdirectory required) - ❌
textparser.hpp(compound word, useparser.hppORtext/parser.hpp) - ❌
mainwindow.hpp(compound word, usewindow/main.hpp) - ❌
mainwindow.cpp(compound word, usewindow/main.cpp)
cmake/directory is for CMake plugins and modules only- Each subdirectory should have its own
CMakeLists.txt
- Define ALL include paths (not done in sub-makefiles)
- Define include path as a variable:
set(GLOBAL_PROJ_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) - Use
include_directories(${GLOBAL_PROJ_INCLUDE_DIR})to set global include path - NEVER define specific header file paths - only directory paths
- This allows
#include <subdir/header.hpp>syntax throughout the project
- Define include path as a variable:
- Handle linking (not done in sub-makefiles)
- All linking must be static - no dynamic linking
- Include subdirectory makefiles
- Only produce
.aartifacts (static libraries) - All linking must be static - no dynamic linking
- ONLY reference the global include directory:
include_directories(${GLOBAL_PROJ_INCLUDE_DIR}) - NEVER define specific header file paths or absolute paths
- Link their own subdirectories - each subdir is responsible for linking its subdirs
- No linking to other peer directories (handled by parent)
- Focus on source compilation and subdirectory linking only
- CMake targets should follow namespace structure
- Target names match directory structure where possible
- Use descriptive target names that reflect the component purpose
Target naming examples:
Single project directory:
# Targets follow namespace: projectname::component
add_library(projectname_component STATIC controller.cpp service.cpp)
add_library(projectname_window STATIC main.cpp)
add_library(projectname_module STATIC processor.cpp handler.cpp)Multiple project directories:
# Targets follow namespace: workspace::projectname::component
add_library(workspace_projectname1_component STATIC controller.cpp service.cpp)
add_library(workspace_projectname1_window STATIC main.cpp)
add_library(workspace_projectname2_handler STATIC request.cpp)
add_library(workspace_projectname2_database STATIC connection.cpp)Target naming rules:
- Single project: Use
projectname_component,projectname_window,projectname_module - Multiple projects: Use
workspace_projectname1_component,workspace_projectname2_handler, etc. - Always prefix with project name for clarity and consistency
- Use underscores to separate all naming components
- Workspace prefix: Include workspace name when multiple projects exist
Example subdirectory CMakeLists.txt:
# Include the same directories as the main project
include_directories(${GLOBAL_PROJ_INCLUDE_DIR})
# Add subdirectories (each subdir links its own subdirs)
add_subdirectory(service)
# Only compile sources to static library
add_library(projectname_component STATIC
source1.cpp
source2.cpp
)
# Link subdirectory libraries
target_link_libraries(projectname_component projectname_service)Every C++ project should include a .clang-format configuration file in the project root:
# .clang-format
NamespaceIndentation: All
UseTab: Never# Format all C++ files in the project
find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
# Preview changes without modifying files
find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format
# Format specific directories
find src -name "*.cpp" | xargs clang-format -i
find include -name "*.hpp" | xargs clang-format -iqmake Integration:
Add to your .pro file:
# clang-format target
clang-format.target = clang-format
clang-format.commands = test -f .clang-format && find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
clang-format.depends = FORCE
QMAKE_EXTRA_TARGETS += clang-format
# clang-format-check target (preview only)
clang-format-check.target = clang-format-check
clang-format-check.commands = test -f .clang-format && find . -name "*.cpp" -o -name "*.hpp" | xargs clang-format
clang-format-check.depends = FORCE
QMAKE_EXTRA_TARGETS += clang-format-checkCMake Integration:
Add to your CMakeLists.txt:
# Check if .clang-format exists
if(EXISTS "${CMAKE_SOURCE_DIR}/.clang-format")
# Find clang-format
find_program(CLANG_FORMAT clang-format)
if(CLANG_FORMAT)
# clang-format target
add_custom_target(clang-format
COMMAND ${CLANG_FORMAT} -i $<TARGET_PROPERTY:SOURCES>
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Formatting code with clang-format"
)
# clang-format-check target (preview only)
add_custom_target(clang-format-check
COMMAND ${CLANG_FORMAT} $<TARGET_PROPERTY:SOURCES>
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Checking code formatting with clang-format"
)
else()
message(WARNING "clang-format not found - formatting targets not available")
endif()
else()
message(WARNING ".clang-format file not found - formatting targets not available")
endif()Usage:
# qmake projects
make clang-format # Format all files
make clang-format-check # Preview changes
# CMake projects
make clang-format # Format all files
make clang-format-check # Preview changes- Consistent code formatting across the entire project
- Automatic namespace indentation with
NamespaceIndentation: All - No tabs with
UseTab: Never(spaces only) - Team consistency - all developers use the same formatting rules
- IDE integration - most editors can use the .clang-format file automatically
- One class per file - each class gets its own header and implementation file
- Single word filenames - no underscores, dashes, spaces, or compound words
- Directory names can be compound:
component/,window/,module/ - File names must be single words:
controller.hpp,service.hpp,manager.hpp - ✅
component/controller.hpp,window/main.hpp,module/processor.hpp - ❌
main_controller.hpp,component-service.hpp,serviceManager.hpp,main controller.hpp,textparser.hpp,service manager.hpp
- Directory names can be compound:
- Use
#include <header.hpp>syntax (angle brackets) - No forward declarations when full includes are available
- Include all necessary headers directly
- All header files must have include guards
- Source files must use .cpp extension
- Header files must use .hpp extension
- Use meaningful variable and function names
- Follow modern C++ conventions and patterns
- Handle unused parameters appropriately
- Class and Struct Names:
MyClassName(PascalCase) - Function Names:
myFuncName(camelCase) - Constants:
ALL_CAPS(UPPER_SNAKE_CASE) - Enums:
ALL_CAPS(UPPER_SNAKE_CASE) - Variables:
myVariableName(camelCase)
projectname/ # Root namespace = "projectname"
├── src/
│ ├── main.cpp
│ ├── parser.cpp # Single class, no subdirectory
│ ├── validator.cpp # Single class, no subdirectory
│ ├── window/ # Compound word "mainwindow" → subdirectory
│ │ ├── CMakeLists.txt
│ │ ├── lib.cpp
│ │ └── main.cpp # MainWindow class
│ ├── component/
│ │ ├── CMakeLists.txt
│ │ ├── lib.cpp
│ │ ├── controller.cpp
│ │ ├── service.cpp
│ │ └── manager/
│ │ ├── CMakeLists.txt
│ │ ├── lib.cpp
│ │ └── cache.cpp
│ └── module/
│ ├── CMakeLists.txt
│ ├── lib.cpp
│ ├── processor.cpp
│ └── handler.cpp
├── include/
│ ├── parser.hpp # Single class, no subdirectory
│ ├── validator.hpp # Single class, no subdirectory
│ ├── window/ # Compound word "mainwindow" → subdirectory
│ │ ├── lib.hpp
│ │ └── main.hpp # MainWindow class
│ ├── component/
│ │ ├── lib.hpp
│ │ ├── controller.hpp
│ │ ├── service.hpp
│ │ └── manager/
│ │ ├── lib.hpp
│ │ └── cache.hpp
│ └── module/
│ ├── lib.hpp
│ ├── processor.hpp
│ └── handler.hpp
├── cmake/
│ └── CompileFont.cmake
├── CMakeLists.txt
└── CONVENTIONS.md
workspace/
├── projectname1/ # Root namespace = "projectname1"
│ ├── src/
│ │ ├── main.cpp
│ │ ├── parser.cpp
│ │ └── window/
│ │ └── main.cpp
│ ├── include/
│ │ ├── parser.hpp
│ │ └── window/
│ │ └── main.hpp
│ └── CMakeLists.txt
├── projectname2/ # Root namespace = "projectname2"
│ ├── src/
│ │ ├── main.cpp
│ │ ├── handler/
│ │ │ └── request.cpp
│ │ └── database/
│ │ └── connection.cpp
│ ├── include/
│ │ ├── handler/
│ │ │ └── request.hpp
│ │ └── database/
│ │ └── connection.hpp
│ └── CMakeLists.txt
└── projectname3/ # Root namespace = "projectname3"
├── src/
│ ├── main.cpp
│ └── engine/
│ └── renderer.cpp
├── include/
│ └── engine/
│ └── renderer.hpp
└── CMakeLists.txt
Namespace mapping examples:
Single project directory:
projectname::parser(fromprojectname/src/parser.cpp)projectname::window::main(fromprojectname/src/window/main.cpp)projectname::component::controller(fromprojectname/src/component/controller.cpp)projectname::module::processor(fromprojectname/src/module/processor.cpp)
Multiple project directories (with workspace):
workspace::projectname1::parser(fromworkspace/projectname1/src/parser.cpp)workspace::projectname1::window::main(fromworkspace/projectname1/src/window/main.cpp)workspace::projectname2::handler::request(fromworkspace/projectname2/src/handler/request.cpp)workspace::projectname2::database::connection(fromworkspace/projectname2/src/database/connection.cpp)workspace::projectname3::engine::renderer(fromworkspace/projectname3/src/engine/renderer.cpp)
When class names contain base class prefixes, remove the prefix and use the base class name as the directory:
Examples:
BaseObject→object/directoryBaseWidget→widget/directoryBaseWindow→window/directoryBaseDialog→dialog/directoryMainWindow→window/main.hpp(compound word)PushButton→button/push.hpp(compound word)
Mapping Rules:
- Single base class:
BaseClass→baseclass/directory - Compound words:
BaseClassSpecific→baseclass/specific.hpp - Multiple related classes: Group by base class in same directory
Directory Structure Examples:
include/
├── object/ # BaseObject-derived classes
│ ├── lib.hpp
│ ├── timer.hpp
│ └── thread.hpp
├── widget/ # BaseWidget-derived classes
│ ├── lib.hpp
│ ├── button.hpp
│ ├── label.hpp
│ └── input.hpp
├── window/ # BaseWindow-derived classes
│ ├── lib.hpp
│ ├── main.hpp # MainWindow → window/main.hpp
│ └── dialog.hpp # BaseDialog → window/dialog.hpp
└── handler/ # Handler-related classes
├── lib.hpp
├── request.hpp
├── response.hpp
└── middleware.hpp
Namespace Mapping:
projectname::object::timer(fromobject/timer.hpp)projectname::widget::button(fromwidget/button.hpp)projectname::window::main(fromwindow/main.hpp)projectname::handler::request(fromhandler/request.hpp)
- Identify base class patterns in your framework (Object, Widget, Window, etc.)
- Create directories based on logical grouping of base classes
- Use compound word rules for complex class names
- Maintain consistent mapping across all framework-derived classes
- Use appropriate design patterns for extensibility
- Keep styling and configuration centralized
- Follow established patterns for the target framework
- Use qmake for Qt projects - qmake is simpler and more straightforward for Qt applications
- Use Qt6 for new projects - Qt6 is the current stable version with modern C++ features
- Avoid CMake for Qt projects - CMake can be overly complex for Qt development
- qmake handles MOC, UIC, and RCC automatically - no manual configuration needed
- qmake project files (.pro) are easier to maintain than CMakeLists.txt for Qt
- Qt6 development tools must be installed
- qmake6 should be available in PATH
- C++23 compatible compiler (clang++ or g++)
# Generate Makefile from .pro file
qmake6 projectname.pro
# Build with parallel compilation
make -j $(nproc)
# Run the application
./projectnameprojectname/
├── Makefile # Generated by qmake6
├── projectname # Executable (after build)
├── *.o # Object files
├── moc_*.cpp # MOC generated files
├── src/ # Source files
├── include/ # Header files
├── projectname.pro # qmake project file
└── resources.qrc # Qt resource file (if used)
# Full build process
qmake6 projectname.pro
make -j $(nproc)
# Clean build
make clean
# Rebuild from scratch
make clean && make -j $(nproc)
# Debug build
qmake6 CONFIG+=debug projectname.pro
make -j $(nproc)
# Release build (default)
qmake6 CONFIG+=release projectname.pro
make -j $(nproc)- Always use
make -j $(nproc)for optimal build performance $(nproc)automatically detects CPU cores for parallel compilation- Significantly faster builds on multi-core systems
- Standard practice for all make-based builds
- QMAKESPEC errors: Use
qmake6instead ofqmakefor Qt6 projects - Missing Qt6: Install Qt6 development packages for your system
- Compiler errors: Ensure C++23 support and proper compiler flags in .pro file
- MOC errors: qmake6 handles MOC automatically, check for missing Q_OBJECT macros
- find_package() - Use CMake's built-in package discovery first
- pkg-config - Use pkg-config as fallback when find_package fails
- Manual search - Only use find_library/find_path as last resort
# Set up pkg-config with proper search paths
find_package(PkgConfig REQUIRED)
set(ENV{PKG_CONFIG_PATH} "/usr/lib/pkgconfig/:/usr/local/lib/pkgconfig/")
# Example pkg-config usage
pkg_check_modules(MYLIB REQUIRED mylib)# Set library search paths for find_library/find_path
set(CMAKE_LIBRARY_PATH "/usr/lib" "/usr/local/lib")
link_directories("/usr/lib" "/usr/local/lib")# Preferred: Use find_package for standard libraries
find_package(OpenSSL REQUIRED)
# Fallback: Use pkg-config for custom libraries
pkg_check_modules(LIBYANG REQUIRED libyang)
# Last resort: Manual library search
find_library(PTHREAD_LIBRARY pthread)
if(PTHREAD_LIBRARY)
set(PTHREAD_FOUND TRUE)
else()
set(PTHREAD_FOUND FALSE)
endif()- Minimum CMake version: 3.31.6
- Use
find_package()to check CMake version in CMakeLists.txt:
cmake_minimum_required(VERSION 3.31.6)- ATF (Automated Testing Framework) for C++ test programs
- Kyua for test execution and management
- Lua format for Kyuafiles
Debian/Ubuntu:
# ATF C++ library and Kyua
sudo apt install libatf-c++-2 kyuaFreeBSD:
# ATF C++ library and Kyua
sudo pkg install atf kyuaArch Linux:
# ATF C++ library and Kyua
sudo pacman -S atf kyuaFedora/RHEL:
# ATF C++ library and Kyua
sudo dnf install atf-c++ kyuamacOS (Homebrew):
# ATF C++ library and Kyua
brew install atf kyuaGentoo:
# ATF C++ library and Kyua
sudo emerge dev-libs/atf app-misc/kyua#include <atf-c++.hpp>
ATF_TEST_CASE(test_functionality);
ATF_TEST_CASE_BODY(test_functionality) {
ATF_REQUIRE(condition);
ATF_REQUIRE_EQ(expected, actual);
ATF_PASS();
}
ATF_INIT_TEST_CASES(tcs) {
ATF_ADD_TEST_CASE(tcs, test_functionality);
}Kyuafiles are Lua scripts that describe test suite structure. Every Kyuafile must start with a version declaration.
-- Kyuafile in Lua format
syntax(2)
-- Define test suite name (optional)
test_suite('projectname')
-- Register ATF test programs
atf_test_program{name="test_program"}- ATF test programs: Use
atf_test_program{name="program_name"} - Plain test programs: Use
plain_test_program{name="program_name"} - TAP test programs: Use
tap_test_program{name="program_name"}
All test program types support optional metadata properties:
atf_test_program{name="test_program",
allowed_architectures='amd64 i386',
required_files='/bin/ls',
timeout=30,
required_user='root'}Common metadata properties:
allowed_architectures- Whitespace-separated list of allowed architecturesallowed_platforms- Whitespace-separated list of allowed platformsrequired_files- Space-separated list of required filesrequired_user- Required user privileges ('root', 'unprivileged')timeout- Test timeout in secondsexecenv- Execution environment (platform-specific options)
syntax(2)
-- Include other Kyuafiles (relative paths only)
include('module-1/Kyuafile')
include('module-2/Kyuafile')For properties with special characters (like dashes):
atf_test_program{name='the_test',
['custom.Bug-Id']='category/12345'}-- Example for systems supporting jail/container environments
atf_test_program{name='network_test',
execenv='jail',
execenv_jail_params='vnet allow.raw_sockets',
required_user='root'}
-- Example for privilege requirements
atf_test_program{name='privileged_test',
required_user='root'}# Create test executable
add_executable(test_program test_program.cpp)
# Link ATF library
find_package(PkgConfig REQUIRED)
pkg_check_modules(ATF REQUIRED atf-c++)
target_link_libraries(test_program ${ATF_LIBRARIES})
target_include_directories(test_program PRIVATE ${ATF_INCLUDE_DIRS})
# Set test output to build root
set_target_properties(test_program PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)
# Copy Kyuafile to build directory (always updated)
configure_file(
${CMAKE_SOURCE_DIR}/tests/Kyuafile
${CMAKE_BINARY_DIR}/Kyuafile
COPYONLY
)
# Create test target (not default)
add_custom_target(run_tests
COMMAND kyua test
DEPENDS test_program
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Running ATF tests with Kyua"
)project/
├── test/ # Test directory adjacent to src/ and include/
│ ├── Kyuafile # Test configuration in Lua format
│ ├── parser.cpp # Test for src/parser.cpp
│ ├── validator.cpp # Test for src/validator.cpp
│ ├── window/ # Mirrors src/window/ structure
│ │ └── main.cpp # Test for src/window/main.cpp
│ ├── component/ # Mirrors src/component/ structure
│ │ ├── controller.cpp # Test for src/component/controller.cpp
│ │ ├── service.cpp # Test for src/component/service.cpp
│ │ └── manager/ # Mirrors src/component/manager/ structure
│ │ └── cache.cpp # Test for src/component/manager/cache.cpp
│ └── module/ # Mirrors src/module/ structure
│ ├── processor.cpp # Test for src/module/processor.cpp
│ └── handler.cpp # Test for src/module/handler.cpp
└── CMakeLists.txt
- test/ directory is adjacent to
src/andinclude/ - Mirror src/ structure - test subdirectories follow the same structure as src/ subdirectories
- Test file naming - use the same filename as the source file being tested
- Namespace mapping - test files use the same namespace as their corresponding source files
# Build tests (not part of default build)
make run_tests
# Run tests manually
cd build/
kyua test
# Run specific test
kyua test test_program:test_functionality
# View test results (after running tests)
kyua report --verbose
# View summary only
kyua report- ATF_REQUIRE(condition) - Fail test if condition is false
- ATF_REQUIRE_EQ(expected, actual) - Require equality
- ATF_CHECK(condition) - Check condition but continue on failure
- ATF_PASS() - Mark test as passed
- ATF_FAIL(reason) - Mark test as failed with reason
- ATF_SKIP(reason) - Skip test with reason
When you get lost with the testing framework, consult these manual pages:
man kyuafile- Kyuafile syntax and configuration optionsman kyua- Kyua test runner commands and optionsman atf-c++- ATF C++ library macros and functions
Quick reference commands:
man kyuafile # Kyuafile configuration syntax
man kyua # Test execution commands
man atf-c++ # ATF C++ programming interface- Write meaningful tests - Test actual functionality, not trivial operations
- Avoid false positives - Tests should fail when code is broken
- Test edge cases - Include boundary conditions and error scenarios
- Verify expected behavior - Use ATF_REQUIRE_EQ for specific value validation
- Test error conditions - Verify proper error handling and exceptions
- All code must compile with C++23 standard
- Follow these conventions strictly
- Update this document when conventions change
- Document any exceptions to these rules
- Tests must be runnable with Kyua framework when requested
SPECIAL CASE: Error classes follow a different pattern than other classes:
- Single file approach:
src/exception.cppandinclude/exception.hpp - Base error class:
ProjectNameBaseError - All errors in same files: Each subsequent error extends the base class in the same files
- No subdirectories: Even if multiple error classes exist
Example structure:
src/exception.cpp # All error implementations
include/exception.hpp # All error declarations
Example error hierarchy:
// include/exception.hpp
namespace projectname {
class ProjectNameBaseError : public std::exception {
// base implementation
};
class ValidationError : public ProjectNameBaseError {
// validation-specific implementation
};
class NetworkError : public ProjectNameBaseError {
// network-specific implementation
};
}Rationale: Errors are typically related and benefit from being in the same files for easier maintenance and consistent error handling patterns.
- Use
std::asyncoverstd::threadwhenever possible - Avoid excessive async usage - C++ async is not always intuitive
- Use async when it truly makes sense - for actual asynchronous operations
- Prefer
std::asyncfor most concurrent operations - Use
std::threadonly whenstd::asyncis not suitable - Avoid over-asyncification - not everything needs to be async
- Consider readability - sometimes synchronous code is clearer
- Use async judiciously - only when there's genuine benefit from concurrency
- I/O operations that can block
- CPU-intensive tasks that can run in parallel
- Independent operations that don't need to wait for each other
- Operations that benefit from non-blocking execution
- Simple operations that don't benefit from concurrency
- Operations with complex interdependencies
- When synchronous code is more readable and maintainable
- Over-engineering simple functionality
- Use library APIs instead of system calls when libraries are available
- Avoid
system(),popen(),exec()family as substitutes for proper library calls - Use native library functions for better error handling, portability, and security
❌ Avoid system calls when libraries exist:
// ❌ Bad - using system() when library exists
system("curl -s https://api.example.com/data");
// ❌ Bad - using popen() when HTTP library exists
FILE* pipe = popen("wget -qO- https://api.example.com/data", "r");
// ❌ Bad - using exec() when JSON library exists
execl("/usr/bin/jq", "jq", ".data", "input.json", NULL);✅ Use proper libraries:
// ✅ Good - use HTTP library (libcurl, httplib, etc.)
auto response = http_client.get("https://api.example.com/data");
// ✅ Good - use JSON library (nlohmann/json, rapidjson, etc.)
auto data = json::parse(file_content);
auto result = data["data"];
// ✅ Good - use native C++ file operations
std::ifstream file("input.json");
json data;
file >> data;- Better error handling - proper exception handling and return codes
- Cross-platform compatibility - libraries handle OS differences
- Security - avoid shell injection vulnerabilities from system calls
- Performance - no process spawning overhead
- Integration - better integration with C++ code and memory management
- Use
constexprraw strings for escape characters, query strings, and text blobs - Define in header files for compile-time availability
- Use raw string literals to avoid escape character issues
❌ Avoid escaped strings:
// ❌ Bad - difficult to read and maintain
const std::string query = "SELECT * FROM users WHERE name = 'John\\'s Data' AND status = \"active\"";
const std::string json = "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}";
const std::string regex = "\\d{4}-\\d{2}-\\d{2}";✅ Use constexpr raw strings in headers:
// ✅ Good - defined in header file
constexpr auto SQL_QUERY = R"(
SELECT * FROM users
WHERE name = 'John's Data'
AND status = "active"
)";
constexpr auto USER_JSON_TEMPLATE = R"({
"name": "John",
"age": 30,
"city": "New York"
})";
constexpr auto DATE_REGEX = R"(\d{4}-\d{2}-\d{2})";
constexpr auto HTML_TEMPLATE = R"(
<html>
<body>
<h1>Welcome</h1>
<p>Hello, {{name}}!</p>
</body>
</html>
)";- Compile-time evaluation - no runtime string construction
- Readable format - preserves original formatting and indentation
- No escape issues - raw strings handle special characters naturally
- Header placement - available at compile time across translation units
- Maintainable - easy to edit complex strings without escape character confusion
- Use
std::regexfor pattern matching instead of manual string operations - Define regex patterns as constexpr raw strings in headers
- Use regex for validation, parsing, and text processing whenever possible
❌ Avoid manual string operations:
// ❌ Bad - manual string parsing and validation
bool isValidEmail(const std::string& email) {
if (email.find('@') == std::string::npos) return false;
if (email.find('.') == std::string::npos) return false;
// More manual checks...
return true;
}
std::string extractNumber(const std::string& text) {
size_t start = text.find_first_of("0123456789");
if (start == std::string::npos) return "";
size_t end = text.find_first_not_of("0123456789", start);
return text.substr(start, end - start);
}✅ Use std::regex:
// ✅ Good - defined in header file
constexpr auto EMAIL_REGEX = R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})";
constexpr auto NUMBER_REGEX = R"(\d+)";
constexpr auto DATE_REGEX = R"(\d{4}-\d{2}-\d{2})";
constexpr auto IP_ADDRESS_REGEX = R"((\d{1,3}\.){3}\d{1,3})";
// ✅ Good - use std::regex for validation and parsing
bool isValidEmail(const std::string& email) {
static const std::regex emailPattern(EMAIL_REGEX);
return std::regex_match(email, emailPattern);
}
std::string extractNumber(const std::string& text) {
static const std::regex numberPattern(NUMBER_REGEX);
std::smatch match;
if (std::regex_search(text, match, numberPattern)) {
return match.str();
}
return "";
}
std::vector<std::string> findDates(const std::string& text) {
static const std::regex datePattern(DATE_REGEX);
std::vector<std::string> dates;
std::sregex_iterator begin(text.begin(), text.end(), datePattern);
std::sregex_iterator end;
for (auto it = begin; it != end; ++it) {
dates.push_back(it->str());
}
return dates;
}- Robust pattern matching - handles complex patterns reliably
- Standard library - no external dependencies
- Performance - optimized regex engine
- Maintainable - clear pattern definitions
- Flexible - supports various regex operations (match, search, replace)
- README.md files - Only create when specifically asked
- Shell scripts - Only create when specifically requested
- Test files - Only create when explicitly asked for ATF tests
- Documentation files - Only create when specifically requested
- .gitignore files - Always create for all projects to exclude build artifacts, temporary files, and IDE-specific files
# Build artifacts
build/
dist/
out/
# CMake artifacts
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.cmake
!CMakeLists.txt
# qmake artifacts
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
object_script_*.Release
object_script_*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user.*
*.autosave
# Compiled Object files
*.o
*.obj
*.so
*.dylib
*.dll
*.exe
*.out
*.app
# IDE and Editor files
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
Thumbs.db
# Temporary files
*.tmp
*.temp
*.log
*.pid
*.seed
*.pid.lock
# Dependency directories
node_modules/
vendor/
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db- Focus on implementing the requested functionality
- Follow conventions for existing files