diff --git a/.github/actions/check-coverage/action.yml b/.github/actions/check-coverage/action.yml new file mode 100644 index 000000000..6089b2112 --- /dev/null +++ b/.github/actions/check-coverage/action.yml @@ -0,0 +1,57 @@ +name: Check Coverage Results +description: 'Extracts a summary of the code coverage results' + +inputs: + binary-dir: + description: 'Directory containing binary files' + required: true + source-dir: + description: 'Directory containing source code files' + default: ./source + +outputs: + ncov_lines: + description: 'Actual number of uncovered lines' + value: ${{ steps.stats.outputs.ncov_lines }} + ncov_functions: + description: 'Actual number of uncovered functions' + value: ${{ steps.stats.outputs.ncov_functions }} + ncov_branches: + description: 'Actual number of uncovered branches' + value: ${{ steps.stats.outputs.ncov_branches }} + +runs: + using: 'composite' + steps: + - name: Capture Results + shell: bash + run: lcov + --capture --rc lcov_branch_coverage=1 + --include '${{ github.workspace }}/*' + --directory '${{ inputs.binary-dir }}' + --output-file '${{ inputs.binary-dir }}/coverage.info' | + tee '${{ inputs.binary-dir }}/lcov_out.txt' + + - name: Generate HTML + shell: bash + run: genhtml + '${{ inputs.binary-dir }}/coverage.info' + --branch-coverage + --output-directory '${{ inputs.binary-dir }}/lcov-html' | + tee '${{ inputs.binary-dir }}/genhtml_out.txt' + + - name: Extract Overall Summary + shell: bash + run: xsltproc --html + '${{ inputs.source-dir }}/.github/actions/check-coverage/lcov-output.xslt' + '${{ inputs.binary-dir }}/lcov-html/index.html' | + tee '${{ inputs.binary-dir }}/lcov-summary.xml' + + - name: Extract Stats + id: stats + shell: bash + run: grep -A 3 "Overall coverage rate" '${{ inputs.binary-dir }}/genhtml_out.txt' | + grep -oP '\([0-9]+ of [0-9]+.*\)' | + tr -d '()' | + awk '{print "ncov_" $4 "=" $3 - $1}' | + tee -a $GITHUB_OUTPUT diff --git a/.github/actions/check-coverage/lcov-output.xslt b/.github/actions/check-coverage/lcov-output.xslt new file mode 100644 index 000000000..77e2ea4dd --- /dev/null +++ b/.github/actions/check-coverage/lcov-output.xslt @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + X + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + +
+ + +
+ + + +

LCOV Report

+ + + + + + + + + + + + +
+ + +
diff --git a/.github/workflows/local_unit_test.yml b/.github/workflows/local_unit_test.yml deleted file mode 100644 index a14a4f1c2..000000000 --- a/.github/workflows/local_unit_test.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: "Local Unit Test" - -on: - push: - pull_request: - -jobs: - #Checks for duplicate actions. Skips push actions if there is a matching or duplicate pull-request action. - check-for-duplicates: - runs-on: ubuntu-latest - # Map a step output to a job output - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: 'same_content' - skip_after_successful_duplicate: 'true' - do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' - - Local-Unit-Test: - needs: check-for-duplicates - if: ${{ needs.check-for-duplicates.outputs.should_skip != 'true' }} - runs-on: ubuntu-20.04 - timeout-minutes: 15 - - steps: - - name: Install coverage tools - run: sudo apt-get install lcov -y - - - name: Checkout submodule - uses: actions/checkout@v3 - - - name: Set up for build - run: | - cp Makefile.sample Makefile - make ENABLE_UNIT_TESTS=true PERMISSIVE_MODE=true prep - - - name: Build the code - run: make -j - - # Baseline lcov and run all tests - - name: Test - run: make test - - - name: Calculate coverage - run: make lcov | tee lcov_out.txt - - - name: Confirm 100% line coverage - run: | - if [[ `grep -A 3 "Overall coverage rate" lcov_out.txt | grep lines` != *"100.0%"* ]]; then - grep -A 3 "Overall coverage rate" lcov_out.txt - echo "Lacks 100.0% line unit test coverage" - exit -1 - fi - - - name: Confirm absolute line coverage - run: | - # Current best possible branch coverage is all but 4, with associated issues for each missing case - missed_branches=4 - coverage_nums=$(grep -A 3 "Overall coverage rate" lcov_out.txt | grep branches | grep -oP "[0-9]+[0-9]*") - - diff=$(echo $coverage_nums | awk '{ print $4 - $3 }') - if [ $diff -gt $missed_branches ] - then - grep -A 3 "Overall coverage rate" lcov_out.txt - echo "More than $missed_branches branches missed" - exit -1 - fi diff --git a/.github/workflows/standalone-build.yml b/.github/workflows/standalone-build.yml new file mode 100644 index 000000000..c66564a8b --- /dev/null +++ b/.github/workflows/standalone-build.yml @@ -0,0 +1,120 @@ +name: Build and Test Standalone OSAL package + +on: + workflow_dispatch: + pull_request: + +defaults: + run: + shell: bash + +env: + allowed_ncov_lines: 0 + allowed_ncov_branches: 4 + allowed_ncov_functions: 0 + +jobs: + + build-and-test: + name: Build and Execute Tests + + strategy: + fail-fast: false + matrix: + build-type: [Debug, Release] + base-os: [ubuntu-22.04, ubuntu-20.04] + + runs-on: ${{ matrix.base-os }} + + steps: + + - name: Checkout OSAL + uses: actions/checkout@v3 + with: + path: source + + - name: Install Coverage Analysis Tools + if: ${{ matrix.build-type == 'Debug' && matrix.base-os == 'ubuntu-20.04' }} + run: sudo apt-get install -y lcov xsltproc && echo "run_lcov=TRUE" >> $GITHUB_ENV + + - name: Set up debug environment + if: ${{ matrix.build-type == 'Debug' }} + run: | + echo "is_debug=TRUE" >> $GITHUB_ENV + echo "is_release=FALSE" >> $GITHUB_ENV + echo "build_tgt=all" >> $GITHUB_ENV + echo "DESTDIR=${{ github.workspace }}/staging-debug" >> $GITHUB_ENV + + - name: Set up release environment + if: ${{ matrix.build-type == 'Release' }} + run: | + echo "is_debug=FALSE" >> $GITHUB_ENV + echo "is_release=TRUE" >> $GITHUB_ENV + echo "build_tgt=install" >> $GITHUB_ENV + echo "DESTDIR=${{ github.workspace }}/staging-release" >> $GITHUB_ENV + + - name: Set up build + run: cmake + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} + -DENABLE_UNIT_TESTS=${{ env.is_debug }} + -DOSAL_OMIT_DEPRECATED=${{ env.is_debug }} + -DOSAL_VALIDATE_API=${{ env.is_release }} + -DOSAL_INSTALL_LIBRARIES=${{ env.is_release }} + -DOSAL_CONFIG_DEBUG_PERMISSIVE_MODE=${{ env.is_debug }} + -DOSAL_SYSTEM_BSPTYPE=generic-linux + -DCMAKE_PREFIX_PATH=/usr/lib/cmake + -DCMAKE_INSTALL_PREFIX=/usr + -S source + -B build + + - name: Build OSAL + working-directory: build + run: make ${{ env.build_tgt }} -j2 + + - name: Validate API + if: ${{ matrix.build-type == 'Release' }} + working-directory: build + run: make osal_apicheck + + - name: Execute Tests + if: ${{ matrix.build-type == 'Debug' }} + working-directory: build + run: ctest --output-on-failure -j4 2>&1 | tee ../ctest.log + + - name: Check Coverage + id: stats + if: ${{ env.run_lcov == 'TRUE' }} + uses: ./source/.github/actions/check-coverage + with: + binary-dir: build + + - name: Enforce coverage function minimum + if: ${{ always() && steps.stats.outputs.ncov_functions > env.allowed_ncov_functions }} + run: | + echo "::error::Too many uncovered functions (${{ steps.stats.outputs.ncov_functions }})" + /bin/false + + - name: Enforce coverage line minimum + if: ${{ always() && steps.stats.outputs.ncov_lines > env.allowed_ncov_lines }} + run: | + echo "::error::Too many uncovered lines (${{ steps.stats.outputs.ncov_lines }})" + /bin/false + + - name: Enforce coverage branch minimum + if: ${{ always() && steps.stats.outputs.ncov_branches > env.allowed_ncov_branches }} + run: | + echo "::error::Too many uncovered branches (${{ steps.stats.outputs.ncov_branches }})" + /bin/false + + - name: Assemble Results + if: ${{ always() }} + run: | + if [ -s ctest.log ]; then + echo '

CTest Execution

' >> $GITHUB_STEP_SUMMARY + echo '
' >> $GITHUB_STEP_SUMMARY
+            cat ctest.log >> $GITHUB_STEP_SUMMARY
+            echo '
' >> $GITHUB_STEP_SUMMARY + fi + if [ -s 'build/lcov-summary.xml' ]; then + cat 'build/lcov-summary.xml' >> $GITHUB_STEP_SUMMARY + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 47a8c10cc..8a75b1c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Development Build: v6.0.0-rc4+dev205 +- fixup API headers for C++ +- workflow to validate OSAL API +- See and + ## Development Build: v6.0.0-rc4+dev199 - Modify unreachable branch in OS_ObjectIdAllocateNew - See diff --git a/CMakeLists.txt b/CMakeLists.txt index 38a39952f..f65266481 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,15 @@ if (CMAKE_VERSION VERSION_GREATER 3.13) cmake_policy(SET CMP0079 NEW) endif() -project(OSAL C) +option(OSAL_VALIDATE_API "Validate the OSAL API headers as part of the build" OFF) + +# If validation is selected, this requires a C++ compiler +set(OSAL_LANG C) +if (OSAL_VALIDATE_API) + list(APPEND OSAL_LANG CXX) +endif() + +project(OSAL ${OSAL_LANG}) # define a custom property to track relationship between BSP and OS # this should be set on BSP "impl" targets to indicate the correct OS impl to go with it @@ -478,3 +486,42 @@ if (OSAL_INSTALL_LIBRARIES) ) endif() + +if (OSAL_VALIDATE_API) + + # Validate the API header files individually + file(GLOB OSAL_API_HEADERS ${OSAL_SOURCE_DIR}/src/os/inc/*.h) + set(OSAL_APICHECK_SOURCES) + set(OSAL_APICHECK_DIR "${CMAKE_CURRENT_BINARY_DIR}/apicheck") + + foreach(HDR_FILE ${OSAL_API_HEADERS}) + get_filename_component(HDR "${HDR_FILE}" NAME) + string(MAKE_C_IDENTIFIER "${HDR}" HDR_ID) + configure_file(${OSAL_SOURCE_DIR}/check_header.c.in ${OSAL_APICHECK_DIR}/check_${HDR_ID}.c) + list(APPEND OSAL_APICHECK_SOURCES_C ${OSAL_APICHECK_DIR}/check_${HDR_ID}.c) + configure_file(${OSAL_SOURCE_DIR}/check_header.cpp.in ${OSAL_APICHECK_DIR}/check_${HDR_ID}.cpp) + list(APPEND OSAL_APICHECK_SOURCES_CXX ${OSAL_APICHECK_DIR}/check_${HDR_ID}.cpp) + endforeach(HDR_FILE ${OSAL_API_HEADERS}) + add_library(osal_apicheck_C STATIC EXCLUDE_FROM_ALL ${OSAL_APICHECK_SOURCES_C}) + add_library(osal_apicheck_CXX STATIC EXCLUDE_FROM_ALL ${OSAL_APICHECK_SOURCES_CXX}) + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(osal_apicheck_C PUBLIC -std=c99 -pedantic -Wall -Werror) + endif() + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(osal_apicheck_CXX PUBLIC -std=c++03 -pedantic -Wall -Werror) + endif() + + # This causes the check to compile with the same set of defines and include dirs as specified + # in the "INTERFACE" properties of the actual module + target_link_libraries(osal_apicheck_C PUBLIC + osal_public_api + ) + target_link_libraries(osal_apicheck_CXX PUBLIC + osal_public_api + ) + + add_custom_target(osal_apicheck) + add_dependencies(osal_apicheck osal_apicheck_C) + add_dependencies(osal_apicheck osal_apicheck_CXX) + +endif() diff --git a/check_header.c.in b/check_header.c.in new file mode 100644 index 000000000..e452d823f --- /dev/null +++ b/check_header.c.in @@ -0,0 +1,4 @@ +#include "@HDR@" + +/* A no-op function so this compilation unit is not empty */ +void CheckHeader_@HDR_ID@(void) {} diff --git a/check_header.cpp.in b/check_header.cpp.in new file mode 100644 index 000000000..4be090c57 --- /dev/null +++ b/check_header.cpp.in @@ -0,0 +1,7 @@ +extern "C" +{ +#include "@HDR@" +} + +/* An empty class */ +class CheckHeader_@HDR_ID@ {}; diff --git a/src/os/inc/osapi-clock.h b/src/os/inc/osapi-clock.h index d41291daf..50cc82c6d 100644 --- a/src/os/inc/osapi-clock.h +++ b/src/os/inc/osapi-clock.h @@ -147,7 +147,8 @@ static inline int64 OS_TimeGetTotalSeconds(OS_time_t tm) */ static inline OS_time_t OS_TimeFromTotalSeconds(int64 tm) { - return (OS_time_t) {.ticks = (tm * OS_TIME_TICKS_PER_SECOND)}; + OS_time_t ostm = {tm * OS_TIME_TICKS_PER_SECOND}; + return ostm; } /*-------------------------------------------------------------------------------------*/ @@ -180,7 +181,8 @@ static inline int64 OS_TimeGetTotalMilliseconds(OS_time_t tm) */ static inline OS_time_t OS_TimeFromTotalMilliseconds(int64 tm) { - return (OS_time_t) {.ticks = (tm * OS_TIME_TICKS_PER_MSEC)}; + OS_time_t ostm = {tm * OS_TIME_TICKS_PER_MSEC}; + return ostm; } /*-------------------------------------------------------------------------------------*/ @@ -213,7 +215,8 @@ static inline int64 OS_TimeGetTotalMicroseconds(OS_time_t tm) */ static inline OS_time_t OS_TimeFromTotalMicroseconds(int64 tm) { - return (OS_time_t) {.ticks = (tm * OS_TIME_TICKS_PER_USEC)}; + OS_time_t ostm = {tm * OS_TIME_TICKS_PER_USEC}; + return ostm; } /*-------------------------------------------------------------------------------------*/ @@ -250,7 +253,8 @@ static inline int64 OS_TimeGetTotalNanoseconds(OS_time_t tm) */ static inline OS_time_t OS_TimeFromTotalNanoseconds(int64 tm) { - return (OS_time_t) {.ticks = (tm / OS_TIME_TICK_RESOLUTION_NS)}; + OS_time_t ostm = {tm / OS_TIME_TICK_RESOLUTION_NS}; + return ostm; } /*-------------------------------------------------------------------------------------*/ @@ -462,7 +466,8 @@ static inline OS_time_t OS_TimeAssembleFromSubseconds(int64 seconds, uint32 subs */ static inline OS_time_t OS_TimeAdd(OS_time_t time1, OS_time_t time2) { - return ((OS_time_t) {time1.ticks + time2.ticks}); + OS_time_t ostm = {time1.ticks + time2.ticks}; + return ostm; } /*-------------------------------------------------------------------------------------*/ @@ -476,7 +481,8 @@ static inline OS_time_t OS_TimeAdd(OS_time_t time1, OS_time_t time2) */ static inline OS_time_t OS_TimeSubtract(OS_time_t time1, OS_time_t time2) { - return ((OS_time_t) {time1.ticks - time2.ticks}); + OS_time_t ostm = {time1.ticks - time2.ticks}; + return ostm; } /**@}*/ diff --git a/src/os/inc/osapi-file.h b/src/os/inc/osapi-file.h index 512157fb5..45ed3f719 100644 --- a/src/os/inc/osapi-file.h +++ b/src/os/inc/osapi-file.h @@ -106,7 +106,7 @@ typedef enum { OS_FILE_FLAG_NONE = 0x00, OS_FILE_FLAG_CREATE = 0x01, - OS_FILE_FLAG_TRUNCATE = 0x02, + OS_FILE_FLAG_TRUNCATE = 0x02 } OS_file_flag_t; /* diff --git a/src/os/inc/osapi-idmap.h b/src/os/inc/osapi-idmap.h index bffa74567..3f1fb8e14 100644 --- a/src/os/inc/osapi-idmap.h +++ b/src/os/inc/osapi-idmap.h @@ -102,10 +102,12 @@ static inline unsigned long OS_ObjectIdToInteger(osal_id_t object_id) static inline osal_id_t OS_ObjectIdFromInteger(unsigned long value) { #ifdef OSAL_OMIT_DEPRECATED - return (osal_id_t) {value}; + osal_id_t idv = {(uint32)value}; #else - return (osal_id_t)value; + osal_id_t idv = (osal_id_t)value; #endif + + return idv; } /*-------------------------------------------------------------------------------------*/ diff --git a/src/os/inc/osapi-macros.h b/src/os/inc/osapi-macros.h index 8db135955..ab1354a8a 100644 --- a/src/os/inc/osapi-macros.h +++ b/src/os/inc/osapi-macros.h @@ -32,6 +32,15 @@ #include "osconfig.h" #include "common_types.h" +/* + * C++ does not support variadic macros until C++11 + * These macros should only be used from C code, not headers + * or inline functions. This ifdef prevents the C++ compiler + * from throwing an error about these definitions - as a result + * these macros are NOT available in C++ source files. + */ +#ifndef __cplusplus + #ifdef OSAL_CONFIG_BUGCHECK_DISABLE /** @@ -145,4 +154,6 @@ */ #define BUGCHECK_VOID(cond) BUGCHECK(cond, ) +#endif /* __cplusplus */ + #endif /* OSAPI_MACROS_H */ diff --git a/src/os/inc/osapi-select.h b/src/os/inc/osapi-select.h index 304dbc623..70dff8282 100644 --- a/src/os/inc/osapi-select.h +++ b/src/os/inc/osapi-select.h @@ -57,7 +57,7 @@ typedef enum OS_STREAM_STATE_BOUND = 0x01, /**< @brief whether the stream is bound */ OS_STREAM_STATE_CONNECTED = 0x02, /**< @brief whether the stream is connected */ OS_STREAM_STATE_READABLE = 0x04, /**< @brief whether the stream is readable */ - OS_STREAM_STATE_WRITABLE = 0x08, /**< @brief whether the stream is writable */ + OS_STREAM_STATE_WRITABLE = 0x08 /**< @brief whether the stream is writable */ } OS_StreamState_t; /** @defgroup OSAPISelect OSAL Select APIs diff --git a/src/os/inc/osapi-version.h b/src/os/inc/osapi-version.h index 50b2ea02f..f72ed4b73 100644 --- a/src/os/inc/osapi-version.h +++ b/src/os/inc/osapi-version.h @@ -34,7 +34,7 @@ /* * Development Build Macro Definitions */ -#define OS_BUILD_NUMBER 199 +#define OS_BUILD_NUMBER 205 #define OS_BUILD_BASELINE "v6.0.0-rc4" /*