diff --git a/CMakeLists.txt b/CMakeLists.txt index faa68dc..65e4880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,19 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/osal_config.cmake") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror=implicit-function-declaration") +# Extra warnings for the library sources only (verified clean on gcc and +# clang). Not applied to tests/examples since cmocka/test code is outside +# our control for some of these (e.g. -Wpedantic on generated test macros). +set(DMOSAL_EXTRA_WARNING_FLAGS + -Wshadow + -Wconversion + -Wpedantic + -Wcast-align + -Wpointer-arith + -Wstrict-prototypes + -Wmissing-prototypes +) + # Sanitizers / coverage — one flag string shared between compile and link. set(_dm_extra "") if(DMOSAL_ENABLE_ASAN) @@ -104,6 +117,7 @@ else() add_library(dmosal STATIC ${osal_SRC}) endif() target_link_libraries(dmosal ${CMAKE_THREAD_LIBS_INIT} rt) +target_compile_options(dmosal PRIVATE ${DMOSAL_EXTRA_WARNING_FLAGS}) set_target_properties(dmosal PROPERTIES VERSION ${DMOSAL_VERSION} SOVERSION ${DMOSAL_MAJOR_VERSION}) @@ -222,11 +236,18 @@ add_subdirectory(examples EXCLUDE_FROM_ALL) # doc ############################################################################## add_custom_target(doc) -find_package(Doxygen) +find_package(Doxygen OPTIONAL_COMPONENTS dot) if(NOT DOXYGEN_FOUND) message(WARNING "Doxygen is not found!") else() - configure_file(doxygen.cfg.in ${PROJECT_BINARY_DIR}/Doxyfile @ONLY) + if(DOXYGEN_DOT_FOUND) + set(DMOSAL_DOXYGEN_HAVE_DOT YES) + else() + set(DMOSAL_DOXYGEN_HAVE_DOT NO) + endif() + set(DMOSAL_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/include/dmosal") + set(DMOSAL_DOCS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/docs") + configure_file(docs/doxygen.cfg.in ${PROJECT_BINARY_DIR}/Doxyfile @ONLY) add_custom_target(doxygen-doc COMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/Doxyfile SOURCES ${PROJECT_BINARY_DIR}/Doxyfile) diff --git a/README.md b/README.md index 19438b7..9ee1a29 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OSAL - OS Abstraction Layer for Embedded Systems +# dmosal — OS Abstraction Layer [![CI](https://github.com/drymon/osal/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/drymon/osal/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/drymon/osal/branch/main/graph/badge.svg)](https://codecov.io/gh/drymon/osal) @@ -7,7 +7,13 @@ A small, portable embedded C abstraction layer providing deterministic/bounded resource management and consistent concurrency, timing, and communication -semantics across POSIX and RTOS platforms. Licensed BSD-2-Clause. +semantics across POSIX and RTOS platforms. + +Everything (tasks, mutexes, semaphores, queues, timers, ...) is allocated +statically up front through a central resource manager, so there's no malloc +on the hot path and the worst-case memory usage is known ahead of time. A +POSIX/Linux backend is included; other targets can be added behind the same +API without breaking existing callers. ``` +-------------------------------+ @@ -27,42 +33,56 @@ semantics across POSIX and RTOS platforms. Licensed BSD-2-Clause. +-------------------------------+ ``` -## Build & test +## Getting started + +You'll need `cmake`, `libcmocka-dev`, and `doxygen`: + +``` +sudo apt install cmake libcmocka-dev doxygen +``` + +Then build the library, run the tests, and check out the examples: + +``` +just check # configure + build + run tests +just build # build the examples target +``` -Requires `cmake`, `libcmocka-dev`, `doxygen` (`sudo apt install cmake -libcmocka-dev doxygen` on Ubuntu/Debian). +If you'd rather not install a toolchain locally, there's a pinned +[`Dockerfile`](Dockerfile): ``` -$ just check # configure + build + run tests +docker build -t dmosal-dev . +docker run --rm -v "$PWD":/workspace -w /workspace dmosal-dev just check ``` -No [`just`](https://github.com/casey/just)? See the [`justfile`](justfile) -for the equivalent raw `cmake`/`ctest` commands, or use the pinned -[`Dockerfile`](Dockerfile) for a reproducible toolchain -(`docker build -t dmosal-dev . && docker run --rm -v "$PWD":/workspace -w -/workspace dmosal-dev just check`). +Don't have [`just`](https://github.com/casey/just) installed? Check the +[`justfile`](justfile) — every recipe there is just a couple of plain +`cmake`/`ctest` commands you can run directly. -Other recipes: `just check-valgrind`, `just check-asan`, `just check-tsan`, -`just check-coverage`, `just doc`, `just format`, `just tidy` — run `just ---list` for the full set. +There are a few more recipes worth knowing about: `just check-valgrind`, +`just check-asan`, `just check-tsan`, `just check-coverage`, `just doc`, +`just format`, `just tidy`. Run `just --list` to see everything. -Resource limits (max tasks, queues, etc.) are configured at CMake time; see -[osal_config.cmake](osal_config.cmake). +Resource limits (how many tasks, queues, etc. you get) are set at CMake +configure time — see [osal_config.cmake](osal_config.cmake). ## Examples -See [examples/](examples/) for sample programs. Build with `just build` +Sample programs live in [examples/](examples/). Build them with `just build` (CMake target `examples`). ## Documentation -- API reference (Doxygen): `just doc`, output in `build/doc/html/`. -- Design/behavioral docs: [docs/](docs/README.md) (architecture, API spec, - resource lifetime, thread-safety, porting guide). +- API reference (Doxygen): run `just doc`, output ends up in + `build/doc/html/`. +- Design docs: [docs/](docs/README.md) — architecture, API spec, resource + lifetime, thread-safety, porting guide. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md). +Bug reports, patches, ideas — all welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) +for how to build, test, and keep the style consistent. ## License diff --git a/docs/doxygen.cfg.in b/docs/doxygen.cfg.in index 89407ee..6c612f0 100644 --- a/docs/doxygen.cfg.in +++ b/docs/doxygen.cfg.in @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "OS Abstract Layer" -PROJECT_NUMBER = +PROJECT_NUMBER = @DMOSAL_VERSION@ PROJECT_BRIEF = "OS Abstract Layer Library API Guide" PROJECT_LOGO = OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc @@ -95,13 +95,13 @@ WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO -WARN_AS_ERROR = NO +WARN_AS_ERROR = YES WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = @DMOSAL_INCLUDE_PATH@ +INPUT = @DMOSAL_INCLUDE_PATH@ @DMOSAL_DOCS_PATH@/mainpage.dox INPUT_ENCODING = UTF-8 FILE_PATTERNS = RECURSIVE = NO @@ -134,7 +134,6 @@ VERBATIM_HEADERS = YES # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES -COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -150,7 +149,6 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -HTML_TIMESTAMP = NO HTML_DYNAMIC_SECTIONS = NO HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = NO @@ -176,12 +174,11 @@ QHG_LOCATION = GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.doxygen.Project DISABLE_INDEX = NO -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -FORMULA_TRANSPARENT = YES USE_MATHJAX = NO MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest @@ -212,9 +209,7 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain -LATEX_TIMESTAMP = NO #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -224,7 +219,6 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = -RTF_SOURCE_CODE = NO #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -244,7 +238,6 @@ XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -DOCBOOK_PROGRAMLISTING = NO #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- @@ -279,13 +272,10 @@ EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES DIA_PATH = HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES +HAVE_DOT = @DMOSAL_DOXYGEN_HAVE_DOT@ DOT_NUM_THREADS = 0 -DOT_FONTNAME = Helvetica -DOT_FONTSIZE = 10 DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES @@ -303,14 +293,13 @@ DOT_IMAGE_FORMAT = png INTERACTIVE_SVG = NO DOT_PATH = DOTFILE_DIRS = -MSCFILE_DIRS = doxywizard +MSCFILE_DIRS = DIAFILE_DIRS = PLANTUML_JAR_PATH = PLANTUML_CFG_FILE = PLANTUML_INCLUDE_PATH = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES diff --git a/docs/mainpage.dox b/docs/mainpage.dox new file mode 100644 index 0000000..cf831e0 --- /dev/null +++ b/docs/mainpage.dox @@ -0,0 +1,21 @@ +/** + * @mainpage dmosal + * + * A small, portable embedded C abstraction layer providing deterministic/bounded + * resource management and consistent concurrency, timing, and communication + * semantics across POSIX and RTOS platforms. + * + * This page indexes the API reference below. For narrative documentation, see + * the Markdown docs in `docs/` (also linked from the project README): + * + * - architecture.md — layered design, subsystem overview, resource-manager + * pattern, callback threading model, backend model. + * - thread-safety.md — per-API table classifying every public call as + * thread-safe, single-owner, or init-thread-only. + * - lifetime.md — init/deinit ordering, resource ownership, when it is + * safe to use/release a resource. + * - api-spec.md — behavioral specification per subsystem: functions, + * states, error codes, invariants. + * - porting.md — how to add a new backend (e.g. an RTOS or bare-metal + * target). + */ diff --git a/doxygen.cfg.in b/doxygen.cfg.in deleted file mode 100644 index 89407ee..0000000 --- a/doxygen.cfg.in +++ /dev/null @@ -1,316 +0,0 @@ -# Doxyfile 1.8.13 - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "OS Abstract Layer" -PROJECT_NUMBER = -PROJECT_BRIEF = "OS Abstract Layer Library API Guide" -PROJECT_LOGO = -OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc -CREATE_SUBDIRS = NO -ALLOW_UNICODE_NAMES = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = ../ -STRIP_FROM_INC_PATH = ../ -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -QT_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 8 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -OPTIMIZE_FOR_FORTRAN = NO -OPTIMIZE_OUTPUT_VHDL = NO -EXTENSION_MAPPING = -MARKDOWN_SUPPORT = YES -TOC_INCLUDE_HEADINGS = 0 -AUTOLINK_SUPPORT = YES -BUILTIN_STL_SUPPORT = NO -CPP_CLI_SUPPORT = NO -SIP_SUPPORT = NO -IDL_PROPERTY_SUPPORT = YES -DISTRIBUTE_GROUP_DOC = NO -GROUP_NESTED_COMPOUNDS = NO -SUBGROUPING = YES -INLINE_GROUPED_CLASSES = NO -INLINE_SIMPLE_STRUCTS = YES -TYPEDEF_HIDES_STRUCT = NO -LOOKUP_CACHE_SIZE = 0 -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = NO -EXTRACT_PRIVATE = NO -EXTRACT_PACKAGE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -HIDE_COMPOUND_REFERENCE= NO -SHOW_INCLUDE_FILES = YES -SHOW_GROUPED_MEMB_INC = NO -FORCE_LOCAL_INCLUDES = NO -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_MEMBERS_CTORS_1ST = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -STRICT_PROTO_MATCHING = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_FILES = YES -SHOW_NAMESPACES = YES -FILE_VERSION_FILTER = -LAYOUT_FILE = -CITE_BIB_FILES = -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_AS_ERROR = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = @DMOSAL_INCLUDE_PATH@ -INPUT_ENCODING = UTF-8 -FILE_PATTERNS = -RECURSIVE = NO -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXCLUDE_SYMBOLS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = * -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = ./ -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -FILTER_SOURCE_PATTERNS = -USE_MDFILE_AS_MAINPAGE = -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES -SOURCE_TOOLTIPS = YES -USE_HTAGS = NO -VERBATIM_HEADERS = YES -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = YES -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_EXTRA_STYLESHEET = -HTML_EXTRA_FILES = -HTML_COLORSTYLE_HUE = 220 -HTML_COLORSTYLE_SAT = 100 -HTML_COLORSTYLE_GAMMA = 80 -HTML_TIMESTAMP = NO -HTML_DYNAMIC_SECTIONS = NO -HTML_INDEX_NUM_ENTRIES = 100 -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = "Doxygen generated docs" -DOCSET_BUNDLE_ID = org.doxygen.Project -DOCSET_PUBLISHER_ID = org.doxygen.Publisher -DOCSET_PUBLISHER_NAME = Publisher -GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -CHM_INDEX_ENCODING = -BINARY_TOC = NO -TOC_EXPAND = NO -GENERATE_QHP = NO -QCH_FILE = -QHP_NAMESPACE = org.doxygen.Project -QHP_VIRTUAL_FOLDER = doc -QHP_CUST_FILTER_NAME = -QHP_CUST_FILTER_ATTRS = -QHP_SECT_FILTER_ATTRS = -QHG_LOCATION = -GENERATE_ECLIPSEHELP = NO -ECLIPSE_DOC_ID = org.doxygen.Project -DISABLE_INDEX = NO -GENERATE_TREEVIEW = NO -ENUM_VALUES_PER_LINE = 4 -TREEVIEW_WIDTH = 250 -EXT_LINKS_IN_WINDOW = NO -FORMULA_FONTSIZE = 10 -FORMULA_TRANSPARENT = YES -USE_MATHJAX = NO -MATHJAX_FORMAT = HTML-CSS -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest -MATHJAX_EXTENSIONS = -MATHJAX_CODEFILE = -SEARCHENGINE = YES -SERVER_BASED_SEARCH = NO -EXTERNAL_SEARCH = NO -SEARCHENGINE_URL = -SEARCHDATA_FILE = searchdata.xml -EXTERNAL_SEARCH_ID = -EXTRA_SEARCH_MAPPINGS = -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = YES -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4 -EXTRA_PACKAGES = -LATEX_HEADER = -LATEX_FOOTER = -LATEX_EXTRA_STYLESHEET = -LATEX_EXTRA_FILES = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -LATEX_SOURCE_CODE = NO -LATEX_BIB_STYLE = plain -LATEX_TIMESTAMP = NO -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -RTF_SOURCE_CODE = NO -#--------------------------------------------------------------------------- -# Configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_SUBDIR = -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# Configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = NO -XML_OUTPUT = xml -XML_PROGRAMLISTING = YES -#--------------------------------------------------------------------------- -# Configuration options related to the DOCBOOK output -#--------------------------------------------------------------------------- -GENERATE_DOCBOOK = NO -DOCBOOK_OUTPUT = docbook -DOCBOOK_PROGRAMLISTING = NO -#--------------------------------------------------------------------------- -# Configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# Configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration options related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -EXTERNAL_PAGES = YES -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES -DIA_PATH = -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES -DOT_NUM_THREADS = 0 -DOT_FONTNAME = Helvetica -DOT_FONTSIZE = 10 -DOT_FONTPATH = -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -UML_LIMIT_NUM_FIELDS = 10 -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -INTERACTIVE_SVG = NO -DOT_PATH = -DOTFILE_DIRS = -MSCFILE_DIRS = doxywizard -DIAFILE_DIRS = -PLANTUML_JAR_PATH = -PLANTUML_CFG_FILE = -PLANTUML_INCLUDE_PATH = -DOT_GRAPH_MAX_NODES = 50 -MAX_DOT_GRAPH_DEPTH = 0 -DOT_TRANSPARENT = NO -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES diff --git a/include/dmosal/osal.h b/include/dmosal/osal.h index 1e1cbd3..d071f48 100644 --- a/include/dmosal/osal.h +++ b/include/dmosal/osal.h @@ -71,7 +71,7 @@ typedef struct { * @return An error code of type ::osal_error_t indicating the status of the * initialization. */ -osal_error_t osal_init(osal_config_t *config); +osal_error_t osal_init(const osal_config_t *config); /** * @brief Retrieves the version of the OS abstraction layer. diff --git a/include/dmosal/osal_lifo.h b/include/dmosal/osal_lifo.h index 046ecd8..95510a5 100644 --- a/include/dmosal/osal_lifo.h +++ b/include/dmosal/osal_lifo.h @@ -93,7 +93,7 @@ osal_lifo_node_t *osal_lifo_pop(osal_lifo_t *lifo); * @param lifo Pointer to the LIFO structure for which the size will be retrieved. * @return Current size of the LIFO. */ -uint32_t osal_lifo_size(osal_lifo_t *lifo); +uint32_t osal_lifo_size(const osal_lifo_t *lifo); /** * @brief Checks if the Last-In-First-Out (LIFO) data structure is empty. @@ -101,7 +101,7 @@ uint32_t osal_lifo_size(osal_lifo_t *lifo); * @param lifo Pointer to the LIFO structure to be checked. * @return true if the LIFO is empty, false otherwise. */ -bool osal_lifo_is_empty(osal_lifo_t *lifo); +bool osal_lifo_is_empty(const osal_lifo_t *lifo); /** * @brief Macro for iterating through a Last-In-First-Out (LIFO) data structure. diff --git a/include/dmosal/osal_log.h b/include/dmosal/osal_log.h index 7a1c94f..aa035be 100644 --- a/include/dmosal/osal_log.h +++ b/include/dmosal/osal_log.h @@ -97,8 +97,13 @@ extern "C" { #define OSALOG_IS_BUILT(level) (OSALOG_BUILD_LEVEL >= OSALOG_LEVEL_##level) /** - * @brief Macro to log FATAL messages with or without timestamp based on - * compilation settings. + * @def OSALOG_FATAL(args, ...) + * @brief Macro to log FATAL messages without a timestamp, compiled out + * when FATAL is above ::OSALOG_BUILD_LEVEL. + * + * @def OSALOGTS_FATAL(args, ...) + * @brief Macro to log FATAL messages with a timestamp, compiled out + * when FATAL is above ::OSALOG_BUILD_LEVEL. */ #if OSALOG_IS_BUILT(FATAL) #define OSALOG_FATAL(args, ...) OSALOG_HELPER(FATAL, false, args, ##__VA_ARGS__) @@ -109,8 +114,13 @@ extern "C" { #endif /** - * @brief Macro to log ERROR messages with or without timestamp based on - * compilation settings. + * @def OSALOG_ERROR(args, ...) + * @brief Macro to log ERROR messages without a timestamp, compiled out + * when ERROR is above ::OSALOG_BUILD_LEVEL. + * + * @def OSALOGTS_ERROR(args, ...) + * @brief Macro to log ERROR messages with a timestamp, compiled out + * when ERROR is above ::OSALOG_BUILD_LEVEL. */ #if OSALOG_IS_BUILT(ERROR) #define OSALOG_ERROR(args, ...) OSALOG_HELPER(ERROR, false, args, ##__VA_ARGS__) @@ -121,8 +131,13 @@ extern "C" { #endif /** - * @brief Macro to log WARN messages with or without timestamp based on - * compilation settings. + * @def OSALOG_WARN(args, ...) + * @brief Macro to log WARN messages without a timestamp, compiled out + * when WARN is above ::OSALOG_BUILD_LEVEL. + * + * @def OSALOGTS_WARN(args, ...) + * @brief Macro to log WARN messages with a timestamp, compiled out + * when WARN is above ::OSALOG_BUILD_LEVEL. */ #if OSALOG_IS_BUILT(WARN) #define OSALOG_WARN(args, ...) OSALOG_HELPER(WARN, false, args, ##__VA_ARGS__) @@ -133,8 +148,13 @@ extern "C" { #endif /** - * @brief Macro to log INFO messages with or without timestamp based on - * compilation settings. + * @def OSALOG_INFO(args, ...) + * @brief Macro to log INFO messages without a timestamp, compiled out + * when INFO is above ::OSALOG_BUILD_LEVEL. + * + * @def OSALOGTS_INFO(args, ...) + * @brief Macro to log INFO messages with a timestamp, compiled out + * when INFO is above ::OSALOG_BUILD_LEVEL. */ #if OSALOG_IS_BUILT(INFO) #define OSALOG_INFO(args, ...) OSALOG_HELPER(INFO, false, args, ##__VA_ARGS__) @@ -145,8 +165,13 @@ extern "C" { #endif /** - * @brief Macro to log DEBUG messages with or without timestamp based on - * compilation settings. + * @def OSALOG_DEBUG(args, ...) + * @brief Macro to log DEBUG messages without a timestamp, compiled out + * when DEBUG is above ::OSALOG_BUILD_LEVEL. + * + * @def OSALOGTS_DEBUG(args, ...) + * @brief Macro to log DEBUG messages with a timestamp, compiled out + * when DEBUG is above ::OSALOG_BUILD_LEVEL. */ #if OSALOG_IS_BUILT(DEBUG) #define OSALOG_DEBUG(args, ...) OSALOG_HELPER(DEBUG, false, args, ##__VA_ARGS__) @@ -157,8 +182,13 @@ extern "C" { #endif /** - * @brief Macro to log TRACE messages with or without timestamp based on - * compilation settings. + * @def OSALOG_TRACE(args, ...) + * @brief Macro to log TRACE messages without a timestamp, compiled out + * when TRACE is above ::OSALOG_BUILD_LEVEL. + * + * @def OSALOGTS_TRACE(args, ...) + * @brief Macro to log TRACE messages with a timestamp, compiled out + * when TRACE is above ::OSALOG_BUILD_LEVEL. */ #if OSALOG_IS_BUILT(TRACE) #define OSALOG_TRACE(args, ...) OSALOG_HELPER(TRACE, false, args, ##__VA_ARGS__) @@ -209,7 +239,8 @@ void osal_log_deinit(void); * @param ts Flag indicating whether to include timestamp. * @return An error code indicating the status of the initialization. */ -osal_error_t osal_log_module_init(uint32_t index, char *name, osal_log_level_t level, bool ts); +osal_error_t +osal_log_module_init(uint32_t index, const char *name, osal_log_level_t level, bool ts); /** * @brief Prints a log message. diff --git a/include/dmosal/osal_queue.h b/include/dmosal/osal_queue.h index 4de6695..83c1658 100644 --- a/include/dmosal/osal_queue.h +++ b/include/dmosal/osal_queue.h @@ -83,7 +83,7 @@ void osal_queue_deinit(void); * @return Pointer to the created queue, or NULL on bad config, pool * exhaustion, a backend error, or a name-clash size mismatch. */ -osal_queue_t *osal_queue_create(osal_queue_cfg_t *cfg); +osal_queue_t *osal_queue_create(const osal_queue_cfg_t *cfg); /** * @brief Deletes a queue from the OS abstraction layer. diff --git a/include/dmosal/osal_rm.h b/include/dmosal/osal_rm.h index 43973ee..470b252 100644 --- a/include/dmosal/osal_rm.h +++ b/include/dmosal/osal_rm.h @@ -79,7 +79,7 @@ typedef struct { * resulting @p rm is not thread-safe (caller must serialize externally). * @return OSAL_E_OK on success, OSAL_E_PARAM if either argument is NULL. */ -osal_error_t osal_rm_init(osal_rm_t *rm, osal_rm_cfg_t *cfg); +osal_error_t osal_rm_init(osal_rm_t *rm, const osal_rm_cfg_t *cfg); /** * @brief Deinitializes the resource manager. @@ -130,7 +130,7 @@ uint32_t osal_rm_use(osal_rm_t *rm); #define OSAL_RM_USEROBJMAN_DECLARE(userobj_type, userobj_num) \ userobj_type userobj[userobj_num]; \ osal_resrc_t resrces[userobj_num]; \ - osal_rm_t rm; + osal_rm_t rm /** * @brief Macro for initializing a user-managed object array with the resource manager. @@ -143,7 +143,7 @@ uint32_t osal_rm_use(osal_rm_t *rm); { \ osal_rm_cfg_t rmcfg; \ int i; \ - int res; \ + osal_error_t res; \ for (i = 0; i < userobj_num; i++) { \ (userobjman_ptr)->resrces[i].data = &(userobjman_ptr)->userobj[i]; \ } \ diff --git a/include/dmosal/osal_task.h b/include/dmosal/osal_task.h index 8d0fe04..3529a9f 100644 --- a/include/dmosal/osal_task.h +++ b/include/dmosal/osal_task.h @@ -87,7 +87,7 @@ void osal_task_deinit(void); * @return Pointer to the created task, or NULL if @p cfg / @c task_handler is * NULL or the task pool is exhausted. */ -osal_task_t *osal_task_create(osal_task_cfg_t *cfg); +osal_task_t *osal_task_create(const osal_task_cfg_t *cfg); /** * @brief Deletes a task from the OS abstraction layer. diff --git a/include/dmosal/osal_time.h b/include/dmosal/osal_time.h index 137b90f..e7c6126 100644 --- a/include/dmosal/osal_time.h +++ b/include/dmosal/osal_time.h @@ -76,7 +76,8 @@ osal_error_t osal_usleep(uint32_t microsec); * real-world wall-clock timestamps. * * @param nsec Output pointer; receives the current clock reading in nanoseconds. - * @return OSAL_E_OK on success, OSAL_E_OSCALL on a backend failure. + * @return OSAL_E_OK on success, OSAL_E_PARAM if @p nsec is NULL, + * OSAL_E_OSCALL on a backend failure. */ osal_error_t osal_clock_time(uint64_t *nsec); diff --git a/include/dmosal/osal_tmcheck.h b/include/dmosal/osal_tmcheck.h index 4e17e22..4578b98 100644 --- a/include/dmosal/osal_tmcheck.h +++ b/include/dmosal/osal_tmcheck.h @@ -114,7 +114,7 @@ void osal_tmcheck_deinit(void); * @param name A unique name identifying the checkpoint. * @return Index of the checkpoint, or -1 on error. */ -int osal_tmcheck_create(char *name); +int osal_tmcheck_create(const char *name); /** * @brief Deletes a previously created time checkpoint. @@ -180,7 +180,7 @@ osal_error_t osal_tmcheck_get_diff(int idx1, int idx2, int64_t *out_ns); * @param name1 Name of the first checkpoint. * @param name2 Name of the second checkpoint. */ -void osal_tmcheck_name_print_diff(char *name1, char *name2); +void osal_tmcheck_name_print_diff(const char *name1, const char *name2); /** * @brief Gets the time difference between two named checkpoints, in nanoseconds. @@ -206,7 +206,7 @@ uint64_t osal_tmcheck_get_captured_ts(int idx); * @param name Name of the checkpoint. * @return Captured timestamp in nanosecond unit */ -uint64_t osal_tmcheck_name_get_captured_ts(char *name); +uint64_t osal_tmcheck_name_get_captured_ts(const char *name); /** * @brief Retrieves the count of used tmcheck points. diff --git a/source/osal.c b/source/osal.c index ecd0e32..b268bbb 100644 --- a/source/osal.c +++ b/source/osal.c @@ -36,7 +36,7 @@ static void log_output_default(char *logstr) printf("%s", logstr); } -osal_error_t osal_init(osal_config_t *config) +osal_error_t osal_init(const osal_config_t *config) { osal_error_t res; osal_log_output_t log_output = log_output_default; diff --git a/source/osal_lifo.c b/source/osal_lifo.c index a5c3a8a..cb17309 100644 --- a/source/osal_lifo.c +++ b/source/osal_lifo.c @@ -58,12 +58,12 @@ osal_lifo_node_t *osal_lifo_pop(osal_lifo_t *lifo) return node; } -uint32_t osal_lifo_size(osal_lifo_t *lifo) +uint32_t osal_lifo_size(const osal_lifo_t *lifo) { return lifo->size; } -bool osal_lifo_is_empty(osal_lifo_t *lifo) +bool osal_lifo_is_empty(const osal_lifo_t *lifo) { return lifo->size == 0; } diff --git a/source/osal_log.c b/source/osal_log.c index 3817ef9..ca7b847 100644 --- a/source/osal_log.c +++ b/source/osal_log.c @@ -70,7 +70,7 @@ osal_log_print(uint32_t index, bool ts, osal_log_level_t level, const char *form va_list ap; char logstr[OSAL_LOG_STRING_SIZE] = {0}; osal_log_module_t *module; - int len; + size_t len; if (index >= OSAL_LOG_MODULE_NUM_MAX) { return OSAL_E_PARAM; @@ -91,8 +91,8 @@ osal_log_print(uint32_t index, bool ts, osal_log_level_t level, const char *form uint32_t sec_low, usec; osal_clock_time(&now); - sec_low = (now / OSAL_SEC_NSEC) % 100000; - usec = (now % OSAL_SEC_NSEC) / OSAL_USEC_NSEC; + sec_low = (uint32_t)((now / OSAL_SEC_NSEC) % 100000); + usec = (uint32_t)((now % OSAL_SEC_NSEC) / OSAL_USEC_NSEC); snprintf(logstr, OSAL_LOG_STRING_SIZE, "%05u-%06u|", sec_low, usec); } @@ -119,7 +119,7 @@ osal_log_print(uint32_t index, bool ts, osal_log_level_t level, const char *form return OSAL_E_OK; } -osal_error_t osal_log_module_init(uint32_t index, char *name, osal_log_level_t level, bool ts) +osal_error_t osal_log_module_init(uint32_t index, const char *name, osal_log_level_t level, bool ts) { osal_log_module_t *module; diff --git a/source/osal_rm.c b/source/osal_rm.c index ddd1f58..cb7e595 100644 --- a/source/osal_rm.c +++ b/source/osal_rm.c @@ -28,7 +28,7 @@ #include "osal_assert.h" #include "osal_rm.h" -osal_error_t osal_rm_init(osal_rm_t *rm, osal_rm_cfg_t *cfg) +osal_error_t osal_rm_init(osal_rm_t *rm, const osal_rm_cfg_t *cfg) { uint32_t i; diff --git a/source/osal_tmcheck.c b/source/osal_tmcheck.c index 95b7856..0fe04f5 100644 --- a/source/osal_tmcheck.c +++ b/source/osal_tmcheck.c @@ -67,7 +67,7 @@ void osal_tmcheck_deinit(void) memset(&s_tmcheck_man, 0, sizeof(s_tmcheck_man)); } -int osal_tmcheck_create(char *name) +int osal_tmcheck_create(const char *name) { int new_idx = -1; tmcheck_t *tmcheck; @@ -178,7 +178,7 @@ void osal_tmcheck_print_all(void) } } -static void tmcheck_print_diff(tmcheck_t *tmcheck1, tmcheck_t *tmcheck2) +static void tmcheck_print_diff(const tmcheck_t *tmcheck1, const tmcheck_t *tmcheck2) { if ((tmcheck1->ts > 0) && (tmcheck2->ts > 0)) { if (tmcheck2->ts > tmcheck1->ts) { @@ -275,7 +275,7 @@ osal_error_t osal_tmcheck_get_diff(int idx1, int idx2, int64_t *out_ns) return OSAL_E_OK; } -void osal_tmcheck_name_print_diff(char *name1, char *name2) +void osal_tmcheck_name_print_diff(const char *name1, const char *name2) { int i; tmcheck_t *tmcheck1 = NULL; @@ -333,7 +333,7 @@ uint64_t osal_tmcheck_get_captured_ts(int idx) return s_tmcheck_man.tmchecks[idx].ts; } -uint64_t osal_tmcheck_name_get_captured_ts(char *name) +uint64_t osal_tmcheck_name_get_captured_ts(const char *name) { int i; tmcheck_t *tmcheck = NULL; diff --git a/source/posix/osal_queue.c b/source/posix/osal_queue.c index cfa5c40..d905c4f 100644 --- a/source/posix/osal_queue.c +++ b/source/posix/osal_queue.c @@ -72,7 +72,7 @@ void osal_queue_deinit(void) s_queue_man.init = false; } -osal_queue_t *osal_queue_create(osal_queue_cfg_t *cfg) +osal_queue_t *osal_queue_create(const osal_queue_cfg_t *cfg) { osal_queue_t *queue; osal_resrc_t *resrc; @@ -186,7 +186,7 @@ osal_queue_recv(osal_queue_t *queue, uint8_t *buf, uint32_t bufsize, uint32_t ti if (FD_ISSET(queue->fd, &rfds) == false) { return OSAL_E_QEMPTY; } - res = mq_receive(queue->fd, (char *)buf, bufsize, NULL); + res = (int)mq_receive(queue->fd, (char *)buf, bufsize, NULL); if (res < 0) { OSALOG_ERROR("mq_receive:%s", strerror(errno)); return OSAL_E_OSCALL; diff --git a/source/posix/osal_sem.c b/source/posix/osal_sem.c index ee0b977..d8d9fe9 100644 --- a/source/posix/osal_sem.c +++ b/source/posix/osal_sem.c @@ -133,8 +133,8 @@ osal_error_t osal_sem_waittime(osal_sem_t *sem, uint32_t usec) } ns = (uint64_t)ts.tv_nsec + ((uint64_t)usec * OSAL_USEC_NSEC); - ts.tv_sec += ns / OSAL_SEC_NSEC; - ts.tv_nsec = ns % OSAL_SEC_NSEC; + ts.tv_sec += (time_t)(ns / OSAL_SEC_NSEC); + ts.tv_nsec = (long)(ns % OSAL_SEC_NSEC); res = sem_timedwait(&sem->psem, &ts); if (res < 0) { diff --git a/source/posix/osal_task.c b/source/posix/osal_task.c index b2c1146..40d5a9f 100644 --- a/source/posix/osal_task.c +++ b/source/posix/osal_task.c @@ -70,7 +70,7 @@ static void *task_run(void *arg) return NULL; } -osal_task_t *osal_task_create(osal_task_cfg_t *cfg) +osal_task_t *osal_task_create(const osal_task_cfg_t *cfg) { osal_task_t *task; osal_resrc_t *resrc; diff --git a/source/posix/osal_time.c b/source/posix/osal_time.c index 23d3ffe..ba42f25 100644 --- a/source/posix/osal_time.c +++ b/source/posix/osal_time.c @@ -45,6 +45,10 @@ osal_error_t osal_clock_time(uint64_t *nsec) { struct timespec nowts; + if (nsec == NULL) { + return OSAL_E_PARAM; + } + if (clock_gettime(CLOCK_MONOTONIC, &nowts) < 0) { perror("clock_gettime"); return OSAL_E_OSCALL; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 65600de..a56c1d0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -73,3 +73,9 @@ add_executable(${TIME_TEST} osal/time_test.c) target_link_libraries(${TIME_TEST} dmosal ${CMOCKA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) add_dependencies(check ${TIME_TEST}) add_test(${TIME_TEST} ${TIME_TEST}) + +set(OSAL_TEST osal_test) +add_executable(${OSAL_TEST} osal/osal_test.c) +target_link_libraries(${OSAL_TEST} dmosal ${CMOCKA_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +add_dependencies(check ${OSAL_TEST}) +add_test(${OSAL_TEST} ${OSAL_TEST}) diff --git a/test/osal/lifo_test.c b/test/osal/lifo_test.c index b0ebd29..520eeab 100644 --- a/test/osal/lifo_test.c +++ b/test/osal/lifo_test.c @@ -68,12 +68,112 @@ static void test_lifo(void **state) assert_null(nodedata); } +static void test_lifo_is_empty(void **state) +{ + (void)state; + osal_lifo_t lifo; + lifo_data_t nodedata; + osal_lifo_node_t *popped; + + osal_lifo_init(&lifo); + assert_true(osal_lifo_is_empty(&lifo)); + + nodedata.data = 42; + osal_lifo_push(&lifo, &nodedata.node); + assert_false(osal_lifo_is_empty(&lifo)); + + popped = osal_lifo_pop(&lifo); + assert_ptr_equal(popped, &nodedata.node); + assert_true(osal_lifo_is_empty(&lifo)); +} + +static void test_lifo_pop_empty_repeated(void **state) +{ + (void)state; + osal_lifo_t lifo; + + osal_lifo_init(&lifo); + + /* Popping an already-empty LIFO repeatedly must stay safe and keep + * returning NULL without corrupting size/head. */ + assert_null(osal_lifo_pop(&lifo)); + assert_null(osal_lifo_pop(&lifo)); + assert_int_equal(osal_lifo_size(&lifo), 0); + assert_true(osal_lifo_is_empty(&lifo)); +} + +static void test_lifo_lifo_order(void **state) +{ + (void)state; + osal_lifo_t lifo; + lifo_data_t a; + lifo_data_t b; + lifo_data_t c; + lifo_data_t *popped; + + osal_lifo_init(&lifo); + a.data = 1; + b.data = 2; + c.data = 3; + + /* Push order a, b, c must pop back in strict reverse order c, b, a. */ + osal_lifo_push(&lifo, &a.node); + osal_lifo_push(&lifo, &b.node); + osal_lifo_push(&lifo, &c.node); + + popped = (lifo_data_t *)osal_lifo_pop(&lifo); + assert_ptr_equal(popped, &c); + popped = (lifo_data_t *)osal_lifo_pop(&lifo); + assert_ptr_equal(popped, &b); + popped = (lifo_data_t *)osal_lifo_pop(&lifo); + assert_ptr_equal(popped, &a); + assert_true(osal_lifo_is_empty(&lifo)); +} + +static void test_lifo_foreach(void **state) +{ + (void)state; + osal_lifo_t lifo; + lifo_data_t nodes[3]; + osal_lifo_node_t *iter; + int seen[3]; + int count = 0; + int i; + + osal_lifo_init(&lifo); + for (i = 0; i < 3; i++) { + nodes[i].data = i; + osal_lifo_push(&lifo, &nodes[i].node); + } + + /* OSAL_LIFO_FOREACH must visit every pushed node exactly once, in + * head-to-tail (LIFO) order, without mutating the structure. */ + OSAL_LIFO_FOREACH(&lifo, iter) + { + lifo_data_t *nodedata = (lifo_data_t *)iter; + assert_true(count < 3); + seen[count] = nodedata->data; + count++; + } + assert_int_equal(count, 3); + assert_int_equal(seen[0], 2); + assert_int_equal(seen[1], 1); + assert_int_equal(seen[2], 0); + + /* The LIFO itself must be untouched by iteration. */ + assert_int_equal(osal_lifo_size(&lifo), 3); +} + int main(void) { setenv("CMOCKA_TEST_ABORT", "1", 1); const struct CMUnitTest tests[] = { cmocka_unit_test(test_lifo), + cmocka_unit_test(test_lifo_is_empty), + cmocka_unit_test(test_lifo_pop_empty_repeated), + cmocka_unit_test(test_lifo_lifo_order), + cmocka_unit_test(test_lifo_foreach), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/log_test.c b/test/osal/log_test.c index 881425f..ef27654 100644 --- a/test/osal/log_test.c +++ b/test/osal/log_test.c @@ -209,6 +209,47 @@ static void test_log_deinit_safety(void **state) assert_int_equal(g_log_calls, 1); } +static void test_log_module_change_invalid(void **state) +{ + (void)state; + osal_error_t res; + + /* Out-of-range index or level must be rejected. */ + res = osal_log_module_change(OSAL_LOG_MODULE_NUM_MAX, OSALOG_LEVEL_INFO); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_log_module_change(TEST_MODULE_IDX, OSALOG_LEVEL_MAX); + assert_int_equal(res, OSAL_E_PARAM); + + /* Changing the level of a module that was never registered (inuse == + * false) must report NOINIT rather than silently "succeeding". */ + res = osal_log_module_change(TEST_MODULE_IDX, OSALOG_LEVEL_INFO); + assert_int_equal(res, OSAL_E_NOINIT); +} + +static void test_log_print_invalid_params(void **state) +{ + (void)state; + osal_error_t res; + + res = osal_log_module_init(TEST_MODULE_IDX, "test", OSALOG_LEVEL_INFO, false); + assert_int_equal(res, OSAL_E_OK); + + /* Out-of-range module index is rejected. Level is only compared + * against the module threshold (no range validation), so an + * excessively verbose level is simply filtered out (no print, but + * still OSAL_E_OK). */ + reset_capture(); + res = osal_log_print(OSAL_LOG_MODULE_NUM_MAX, false, OSALOG_LEVEL_INFO, "x\n"); + assert_int_equal(res, OSAL_E_PARAM); + assert_int_equal(g_log_calls, 0); + + reset_capture(); + res = osal_log_print(TEST_MODULE_IDX, false, OSALOG_LEVEL_MAX, "x\n"); + assert_int_equal(res, OSAL_E_OK); + assert_int_equal(g_log_calls, 0); +} + static int setup_clean(void **state) { (void)state; @@ -245,6 +286,8 @@ int main(void) cmocka_unit_test_setup_teardown(test_log_macros, setup_ready, teardown), cmocka_unit_test_setup_teardown(test_log_print_unregistered_module, setup_ready, teardown), cmocka_unit_test_setup_teardown(test_log_deinit_safety, setup_ready, teardown), + cmocka_unit_test_setup_teardown(test_log_module_change_invalid, setup_ready, teardown), + cmocka_unit_test_setup_teardown(test_log_print_invalid_params, setup_ready, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/mutex_test.c b/test/osal/mutex_test.c index 05a9928..8522ab7 100644 --- a/test/osal/mutex_test.c +++ b/test/osal/mutex_test.c @@ -94,6 +94,25 @@ static void test_mutex_loop(void) osal_mutex_deinit(); } +static void test_mutex_null_params(void **state) +{ + (void)state; + int res; + + (void)state; + + /* NULL-pointer parameter paths for the lock/unlock/delete APIs must + * be rejected safely rather than crashing. */ + res = osal_mutex_lock(NULL); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_mutex_unlock(NULL); + assert_int_equal(res, OSAL_E_PARAM); + + /* delete(NULL) is documented as a safe no-op. */ + osal_mutex_delete(NULL); +} + static void test_mutex(void **state) { int i; @@ -109,6 +128,7 @@ int main(void) const struct CMUnitTest tests[] = { cmocka_unit_test(test_mutex), + cmocka_unit_test(test_mutex_null_params), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/osal_test.c b/test/osal/osal_test.c new file mode 100644 index 0000000..ec24611 --- /dev/null +++ b/test/osal/osal_test.c @@ -0,0 +1,144 @@ +/* BSD 2-Clause License +* +* Copyright (c) 2026, nguyenvannam142@gmail.com +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "cmocka_include.h" +#include "osal.h" + +static char g_log_buf[4096]; +static int g_log_calls; + +static void test_log_output(char *logstr) +{ + size_t used = strlen(g_log_buf); + g_log_calls++; + snprintf(g_log_buf + used, sizeof(g_log_buf) - used, "%s", logstr); +} + +static void reset_capture(void) +{ + g_log_calls = 0; + g_log_buf[0] = 0; +} + +static void test_osal_version(void **state) +{ + (void)state; + char *ver; + + ver = osal_version(); + assert_non_null(ver); + assert_true(strlen(ver) > 0); +} + +static void test_osal_init_default_config(void **state) +{ + (void)state; + osal_error_t res; + + /* NULL config falls back to built-in defaults. */ + res = osal_init(NULL); + assert_int_equal(res, OSAL_E_OK); + + osal_deinit(); +} + +static void test_osal_init_custom_config(void **state) +{ + (void)state; + osal_error_t res; + osal_config_t cfg; + + memset(&cfg, 0, sizeof(cfg)); + cfg.log_output = test_log_output; + cfg.osal_level = OSALOG_LEVEL_INFO; + + res = osal_init(&cfg); + assert_int_equal(res, OSAL_E_OK); + + /* Re-init while already initialized is a no-op: the config argument + * is ignored and the subsystem stays usable. */ + res = osal_init(NULL); + assert_int_equal(res, OSAL_E_OK); + + osal_deinit(); + + /* Deinit again must be idempotent / safe. */ + osal_deinit(); +} + +static void test_osal_init_config_null_log_output(void **state) +{ + (void)state; + osal_error_t res; + osal_config_t cfg; + + /* A non-NULL config with a NULL log_output field must still fall + * back to the default writer rather than leaving logging broken. */ + memset(&cfg, 0, sizeof(cfg)); + cfg.log_output = NULL; + cfg.osal_level = OSALOG_LEVEL_INFO; + + res = osal_init(&cfg); + assert_int_equal(res, OSAL_E_OK); + + osal_deinit(); +} + +static void test_osal_print_resource(void **state) +{ + (void)state; + osal_error_t res; + osal_config_t cfg; + + memset(&cfg, 0, sizeof(cfg)); + cfg.log_output = test_log_output; + cfg.osal_level = OSALOG_LEVEL_INFO; + + res = osal_init(&cfg); + assert_int_equal(res, OSAL_E_OK); + + reset_capture(); + osal_print_resource(); + /* One header line + one line per subsystem + one footer line. */ + assert_true(g_log_calls >= 7); + assert_non_null(strstr(g_log_buf, "tmcheck")); + + osal_deinit(); +} + +int main(void) +{ + setenv("CMOCKA_TEST_ABORT", "1", 1); + + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_osal_version), + cmocka_unit_test(test_osal_init_default_config), + cmocka_unit_test(test_osal_init_custom_config), + cmocka_unit_test(test_osal_init_config_null_log_output), + cmocka_unit_test(test_osal_print_resource), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/osal/queue_test.c b/test/osal/queue_test.c index 4e6db0f..640e5be 100644 --- a/test/osal/queue_test.c +++ b/test/osal/queue_test.c @@ -285,6 +285,56 @@ static void test_queue_pool_exhaustion(void **state) } } +static void test_queue_send_recv_invalid_params(void **state) +{ + (void)state; + osal_queue_cfg_t cfg; + osal_queue_t *queue; + osal_error_t res; + uint8_t msg[16] = "abc"; + uint8_t buf[16]; + + memset(&cfg, 0, sizeof(cfg)); + unique_name((char *)cfg.name, sizeof(cfg.name), "inv_params", 0); + cfg.msglen = sizeof(buf); + cfg.qsize = 2; + + queue = osal_queue_create(&cfg); + assert_non_null(queue); + + res = osal_queue_send(NULL, msg, sizeof(msg)); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_queue_send(queue, NULL, sizeof(msg)); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_queue_send(queue, msg, 0); + assert_int_equal(res, OSAL_E_PARAM); + + /* A message larger than the queue's configured msglen is rejected. */ + res = osal_queue_send(queue, msg, cfg.msglen + 1); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_queue_recv(NULL, buf, sizeof(buf), 1000); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_queue_recv(queue, NULL, sizeof(buf), 1000); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_queue_recv(queue, buf, 0, 1000); + assert_int_equal(res, OSAL_E_PARAM); + + osal_queue_delete(queue); +} + +static void test_queue_delete_null(void **state) +{ + (void)state; + + /* delete(NULL) is documented as a safe no-op. */ + osal_queue_delete(NULL); +} + static int setup(void **state) { (void)state; @@ -313,6 +363,8 @@ int main(void) cmocka_unit_test_setup_teardown(test_queue_reopen_mismatch, setup, teardown), cmocka_unit_test_setup_teardown(test_queue_delete_unlinks, setup, teardown), cmocka_unit_test_setup_teardown(test_queue_pool_exhaustion, setup, teardown), + cmocka_unit_test_setup_teardown(test_queue_send_recv_invalid_params, setup, teardown), + cmocka_unit_test_setup_teardown(test_queue_delete_null, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/rm_test.c b/test/osal/rm_test.c index 53ac7a7..4dcf90b 100644 --- a/test/osal/rm_test.c +++ b/test/osal/rm_test.c @@ -108,6 +108,33 @@ static void test_rm(bool use_mutex) } } +static void test_rm_null_params(void **state) +{ + (void)state; + osal_rm_t rm; + osal_rm_cfg_t rmcfg; + osal_resrc_t resrc; + osal_error_t err; + + memset(&rmcfg, 0, sizeof(rmcfg)); + + err = osal_rm_init(NULL, &rmcfg); + assert_int_equal(err, OSAL_E_PARAM); + + err = osal_rm_init(&rm, NULL); + assert_int_equal(err, OSAL_E_PARAM); + + /* NULL-pointer parameter paths for the remaining APIs must be + * rejected/no-op safely rather than crashing. */ + assert_null(osal_rm_alloc(NULL)); + assert_int_equal(osal_rm_avail(NULL), 0); + + osal_rm_free(NULL, &resrc); + osal_rm_free(&rm, NULL); + + osal_rm_deinit(NULL); +} + static void test_rm_run(void **state) { (void)state; @@ -125,6 +152,7 @@ int main(void) const struct CMUnitTest tests[] = { cmocka_unit_test(test_rm_run), + cmocka_unit_test(test_rm_null_params), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/sem_test.c b/test/osal/sem_test.c index 8cb7721..6d9245b 100644 --- a/test/osal/sem_test.c +++ b/test/osal/sem_test.c @@ -125,6 +125,25 @@ static void test_sem(void **state) } } +static void test_sem_null_params(void **state) +{ + (void)state; + osal_error_t res; + + /* NULL-pointer parameter paths must be rejected safely. */ + res = osal_sem_post(NULL); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_sem_wait(NULL); + assert_int_equal(res, OSAL_E_PARAM); + + res = osal_sem_waittime(NULL, 1000); + assert_int_equal(res, OSAL_E_PARAM); + + /* delete(NULL) is documented as a safe no-op. */ + osal_sem_delete(NULL); +} + static int setup(void **state) { (void)state; @@ -145,6 +164,7 @@ int main(void) const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(test_sem, setup, teardown), + cmocka_unit_test_setup_teardown(test_sem_null_params, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/task_test.c b/test/osal/task_test.c index a2b140a..684810f 100644 --- a/test/osal/task_test.c +++ b/test/osal/task_test.c @@ -136,6 +136,14 @@ static void test_task_delete(void **state) osal_task_delete(task); } +static void test_task_delete_null(void **state) +{ + (void)state; + + /* delete(NULL) is documented as a safe no-op. */ + osal_task_delete(NULL); +} + static int setup(void **state) { (void)state; @@ -163,6 +171,7 @@ int main(void) cmocka_unit_test_setup_teardown(test_task_init, setup, teardown), cmocka_unit_test_setup_teardown(test_task_create, setup, teardown), cmocka_unit_test_setup_teardown(test_task_delete, setup, teardown), + cmocka_unit_test_setup_teardown(test_task_delete_null, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/time_test.c b/test/osal/time_test.c index b021f76..f825796 100644 --- a/test/osal/time_test.c +++ b/test/osal/time_test.c @@ -90,6 +90,15 @@ static void test_sleep_zero_returns(void **state) assert_true((ts2 - ts1) < OSAL_SEC_NSEC); } +static void test_clock_time_null_param(void **state) +{ + (void)state; + osal_error_t res; + + res = osal_clock_time(NULL); + assert_int_equal(res, OSAL_E_PARAM); +} + int main(void) { setenv("CMOCKA_TEST_ABORT", "1", 1); @@ -98,6 +107,7 @@ int main(void) cmocka_unit_test(test_clock_time_nondecreasing), cmocka_unit_test(test_usleep_duration), cmocka_unit_test(test_sleep_zero_returns), + cmocka_unit_test(test_clock_time_null_param), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/test/osal/tmcheck_test.c b/test/osal/tmcheck_test.c index e332ca1..6c13959 100644 --- a/test/osal/tmcheck_test.c +++ b/test/osal/tmcheck_test.c @@ -33,6 +33,21 @@ static void unique_name(char *out, size_t outsize, const char *tag, int idx) snprintf(out, outsize, "tmc_%d_%s_%d", (int)getpid(), tag, idx); } +static char g_log_buf[OSAL_LOG_STRING_SIZE]; +static int g_log_calls; + +static void capture_log_output(char *logstr) +{ + g_log_calls++; + snprintf(g_log_buf, sizeof(g_log_buf), "%s", logstr); +} + +static void reset_capture(void) +{ + g_log_calls = 0; + g_log_buf[0] = 0; +} + static void test_tmcheck_init(void **state) { (void)state; @@ -314,10 +329,224 @@ static void test_tmcheck_multithread_capture(void **state) } } +static void test_tmcheck_print_diff(void **state) +{ + (void)state; + int idx1; + int idx2; + char name1[32]; + char name2[32]; + + unique_name(name1, sizeof(name1), "pdiff_a", 0); + unique_name(name2, sizeof(name2), "pdiff_b", 0); + idx1 = osal_tmcheck_create(name1); + assert_true(idx1 >= 0); + idx2 = osal_tmcheck_create(name2); + assert_true(idx2 >= 0); + + osal_tmcheck_capture_ts(idx1); + usleep(20000); + osal_tmcheck_capture_ts(idx2); + + reset_capture(); + osal_tmcheck_print_diff(idx1, idx2); + assert_int_equal(g_log_calls, 1); + assert_non_null(strstr(g_log_buf, name1)); + assert_non_null(strstr(g_log_buf, name2)); + + /* Invalid indices are rejected: validate_idx() itself logs one error + * line, but no diff line is emitted for it. */ + reset_capture(); + osal_tmcheck_print_diff(-1, idx2); + assert_int_equal(g_log_calls, 1); + assert_null(strstr(g_log_buf, name2)); + + osal_tmcheck_delete(idx1); + osal_tmcheck_delete(idx2); +} + +static void test_tmcheck_print_diff_uncaptured(void **state) +{ + (void)state; + int idx1; + int idx2; + char name1[32]; + char name2[32]; + + unique_name(name1, sizeof(name1), "pdiff_unc_a", 0); + unique_name(name2, sizeof(name2), "pdiff_unc_b", 0); + idx1 = osal_tmcheck_create(name1); + assert_true(idx1 >= 0); + idx2 = osal_tmcheck_create(name2); + assert_true(idx2 >= 0); + + /* Neither checkpoint has been captured (ts == 0): print_diff must be a + * silent no-op rather than printing a bogus "0 ns" diff. */ + reset_capture(); + osal_tmcheck_print_diff(idx1, idx2); + assert_int_equal(g_log_calls, 0); + + osal_tmcheck_delete(idx1); + osal_tmcheck_delete(idx2); +} + +static void test_tmcheck_print_all(void **state) +{ + (void)state; + int idx; + char name[32]; + + unique_name(name, sizeof(name), "pall", 0); + idx = osal_tmcheck_create(name); + assert_true(idx >= 0); + + /* Uncaptured checkpoint (ts == 0) must not be printed. */ + reset_capture(); + osal_tmcheck_print_all(); + assert_int_equal(g_log_calls, 0); + + osal_tmcheck_capture_ts(idx); + reset_capture(); + osal_tmcheck_print_all(); + assert_true(g_log_calls >= 1); + assert_non_null(strstr(g_log_buf, name)); + + osal_tmcheck_delete(idx); +} + +static void test_tmcheck_print_diff_all(void **state) +{ + (void)state; + int idx1; + int idx2; + int idx3; + char name1[32]; + char name2[32]; + char name3[32]; + + unique_name(name1, sizeof(name1), "pdiffall_a", 0); + unique_name(name2, sizeof(name2), "pdiffall_b", 0); + unique_name(name3, sizeof(name3), "pdiffall_c", 0); + idx1 = osal_tmcheck_create(name1); + assert_true(idx1 >= 0); + idx2 = osal_tmcheck_create(name2); + assert_true(idx2 >= 0); + idx3 = osal_tmcheck_create(name3); + assert_true(idx3 >= 0); + + osal_tmcheck_capture_ts(idx1); + usleep(10000); + osal_tmcheck_capture_ts(idx2); + usleep(10000); + osal_tmcheck_capture_ts(idx3); + + /* With 3 captured checkpoints, print_diff_all prints consecutive + * diffs plus a final wrap-around (last-to-first) diff: 3 lines total. */ + reset_capture(); + osal_tmcheck_print_diff_all(); + assert_true(g_log_calls >= 3); + + osal_tmcheck_delete(idx1); + osal_tmcheck_delete(idx2); + osal_tmcheck_delete(idx3); +} + +static void test_tmcheck_reset_all(void **state) +{ + (void)state; + int idx1; + int idx2; + char name1[32]; + char name2[32]; + + unique_name(name1, sizeof(name1), "resetall_a", 0); + unique_name(name2, sizeof(name2), "resetall_b", 0); + idx1 = osal_tmcheck_create(name1); + assert_true(idx1 >= 0); + idx2 = osal_tmcheck_create(name2); + assert_true(idx2 >= 0); + + osal_tmcheck_capture_ts(idx1); + osal_tmcheck_capture_ts(idx2); + assert_true(osal_tmcheck_get_captured_ts(idx1) > 0); + assert_true(osal_tmcheck_get_captured_ts(idx2) > 0); + + osal_tmcheck_reset_all(); + assert_int_equal(osal_tmcheck_get_captured_ts(idx1), 0); + assert_int_equal(osal_tmcheck_get_captured_ts(idx2), 0); + + /* Checkpoints remain allocated after reset_all (only ts is cleared). */ + assert_int_equal(osal_tmcheck_use(), 2u); + + osal_tmcheck_delete(idx1); + osal_tmcheck_delete(idx2); +} + +static void test_tmcheck_name_print_diff(void **state) +{ + (void)state; + int idx1; + int idx2; + char name1[32]; + char name2[32]; + + unique_name(name1, sizeof(name1), "npdiff_a", 0); + unique_name(name2, sizeof(name2), "npdiff_b", 0); + idx1 = osal_tmcheck_create(name1); + assert_true(idx1 >= 0); + idx2 = osal_tmcheck_create(name2); + assert_true(idx2 >= 0); + + osal_tmcheck_capture_ts(idx1); + usleep(20000); + osal_tmcheck_capture_ts(idx2); + + reset_capture(); + osal_tmcheck_name_print_diff(name1, name2); + assert_int_equal(g_log_calls, 1); + assert_non_null(strstr(g_log_buf, name1)); + assert_non_null(strstr(g_log_buf, name2)); + + /* An unknown checkpoint name must log an error, not a diff line, and + * must not crash on a NULL name. */ + reset_capture(); + osal_tmcheck_name_print_diff(name1, "no-such-checkpoint"); + assert_int_equal(g_log_calls, 1); + assert_null(strstr(g_log_buf, name2)); + + osal_tmcheck_delete(idx1); + osal_tmcheck_delete(idx2); +} + +static void test_tmcheck_get_captured_ts_invalid(void **state) +{ + (void)state; + + assert_int_equal(osal_tmcheck_get_captured_ts(-1), 0); + assert_int_equal(osal_tmcheck_get_captured_ts(OSAL_TMCHECK_NUM_MAX), 0); +} + +static void test_tmcheck_name_get_captured_ts_invalid(void **state) +{ + (void)state; + uint64_t ts; + + reset_capture(); + ts = osal_tmcheck_name_get_captured_ts("no-such-checkpoint"); + assert_int_equal(ts, 0); + assert_int_equal(g_log_calls, 1); +} + static int setup(void **state) { + osal_config_t cfg; + (void)state; - osal_init(NULL); + memset(&cfg, 0, sizeof(cfg)); + cfg.log_output = capture_log_output; + cfg.osal_level = OSALOG_LEVEL_INFO; + osal_init(&cfg); + reset_capture(); return 0; } @@ -343,6 +572,14 @@ int main(void) cmocka_unit_test_setup_teardown(test_tmcheck_name_apis, setup, teardown), cmocka_unit_test_setup_teardown(test_tmcheck_diff_ns_large, setup, teardown), cmocka_unit_test_setup_teardown(test_tmcheck_multithread_capture, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_print_diff, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_print_diff_uncaptured, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_print_all, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_print_diff_all, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_reset_all, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_name_print_diff, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_get_captured_ts_invalid, setup, teardown), + cmocka_unit_test_setup_teardown(test_tmcheck_name_get_captured_ts_invalid, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); }