diff --git a/.github/workflows/codeql_c.yml b/.github/workflows/codeql_c.yml index a2fa9a0..3436c22 100644 --- a/.github/workflows/codeql_c.yml +++ b/.github/workflows/codeql_c.yml @@ -15,17 +15,8 @@ on: push: branches: ["main"] paths: - - .github/workflows/codeql_c.yml - - bindings/c/cmocka/** - - bindings/c/corrosion/** - - bindings/c/include/** - - bindings/c/src/** - - bindings/c/tests/** - - bindings/c/build.rs - - bindings/c/Cargo.toml - - bindings/c/Cargo.lock - - bindings/c/cbindgen.toml - - bindings/c/CMakeLists.txt + - .github/workflows/*_c.yml + - bindings/c/** - src/** - Cargo.lock - Cargo.toml @@ -35,18 +26,9 @@ on: # The branches below must be a subset of the branches above branches: ["main"] paths: - - .github/workflows/codeql_c.yml - - bindings/c/cmocka/** - - bindings/c/corrosion/** - - bindings/c/include/** - - bindings/c/src/** - - bindings/c/tests/** - - bindings/c/build.rs - - bindings/c/Cargo.toml - - bindings/c/Cargo.lock - - bindings/c/cbindgen.toml - - bindings/c/CMakeLists.txt - - src/* + - .github/workflows/*_c.yml + - bindings/c/** + - src/** - Cargo.lock - Cargo.toml - deny.toml @@ -87,7 +69,7 @@ jobs: working-directory: bindings/c/build run: | # Make cmocka a static dependency when testing. - cmake ../ -DBUILD_SHARED_LIBS=OFF + cmake ../ -DBUILD_SHARED_LIBS=OFF -DBUILD_CCHECKS_TESTS=ON - name: CMake Build working-directory: bindings/c/build run: | diff --git a/.github/workflows/codeql_cpp.yml b/.github/workflows/codeql_cpp.yml new file mode 100644 index 0000000..b646fd5 --- /dev/null +++ b/.github/workflows/codeql_cpp.yml @@ -0,0 +1,82 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: ["main"] + paths: + - .github/workflows/*_cpp.yml + - bindings/cpp/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules + pull_request: + # The branches below must be a subset of the branches above + branches: ["main"] + paths: + - .github/workflows/*_cpp.yml + - bindings/cpp/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules + schedule: + - cron: "0 0 * * 1" + +jobs: + analyze_cpp: + name: Analyze C++ + defaults: + run: + working-directory: bindings/cpp + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: cpp + + # Build C++ Project + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Make Build Directory + run: mkdir -p build + - name: CMake Configure + working-directory: bindings/cpp/build + run: | + # Make cmocka a static dependency when testing. + cmake ../ -DBUILD_SHARED_LIBS=OFF -DBUILD_CPPCHECKS_TESTS=ON + - name: CMake Build + working-directory: bindings/cpp/build + run: | + cmake --build . --config=Debug + + # Run the analysis + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:cpp" diff --git a/.github/workflows/codeql_python.yml b/.github/workflows/codeql_python.yml index d033b8c..bd51b2e 100644 --- a/.github/workflows/codeql_python.yml +++ b/.github/workflows/codeql_python.yml @@ -15,20 +15,24 @@ on: push: branches: ["main"] paths: - - .github/workflows/codeql_python.yml - - bindings/python/pychecks/** - - bindings/python/tests/** - - bindings/python/conftest.py - - bindings/python/pyproject.toml + - .github/workflows/*_python.yml + - bindings/python/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules pull_request: # The branches below must be a subset of the branches above branches: ["main"] paths: - - .github/workflows/codeql_python.yml - - bindings/python/pychecks/** - - bindings/python/tests/** - - bindings/python/conftest.py - - bindings/python/pyproject.toml + - .github/workflows/*_python.yml + - bindings/python/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules schedule: - cron: "0 0 * * 1" diff --git a/.github/workflows/rust-audit.yml b/.github/workflows/rust-audit.yml index 2f857f1..59d1709 100644 --- a/.github/workflows/rust-audit.yml +++ b/.github/workflows/rust-audit.yml @@ -26,6 +26,19 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: EmbarkStudios/cargo-deny-action@v1 + audit_cpp: + name: Audit C++ + runs-on: ubuntu-latest + defaults: + run: + working-directory: bindings/cpp + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: EmbarkStudios/cargo-deny-action@v1 audit_python: name: Audit Python runs-on: ubuntu-latest diff --git a/.github/workflows/test_suite_c.yml b/.github/workflows/test_suite_c.yml index 40d8a88..84f0c1b 100644 --- a/.github/workflows/test_suite_c.yml +++ b/.github/workflows/test_suite_c.yml @@ -3,17 +3,8 @@ name: C Test Suite on: push: paths: - - .github/workflows/test_suite_c.yml - - bindings/c/cmocka/** - - bindings/c/corrosion/** - - bindings/c/include/** - - bindings/c/src/** - - bindings/c/tests/** - - bindings/c/build.rs - - bindings/c/Cargo.toml - - bindings/c/Cargo.lock - - bindings/c/cbindgen.toml - - bindings/c/CMakeLists.txt + - .github/workflows/*_c.yml + - bindings/c/** - src/** - Cargo.lock - Cargo.toml @@ -21,17 +12,8 @@ on: - .gitmodules pull_request: paths: - - .github/workflows/test_suite_c.yml - - bindings/c/cmocka/** - - bindings/c/corrosion/** - - bindings/c/include/** - - bindings/c/src/** - - bindings/c/tests/** - - bindings/c/build.rs - - bindings/c/Cargo.toml - - bindings/c/Cargo.lock - - bindings/c/cbindgen.toml - - bindings/c/CMakeLists.txt + - .github/workflows/*_c.yml + - bindings/c/** - src/** - Cargo.lock - Cargo.toml @@ -67,7 +49,7 @@ jobs: working-directory: bindings/c/build run: | # Make cmocka a static dependency when testing. - cmake ../ -DBUILD_SHARED_LIBS=OFF -DCODE_COVERAGE=ON + cmake ../ -DBUILD_SHARED_LIBS=OFF -DBUILD_CCHECKS_TESTS=ON - name: CMake Build working-directory: bindings/c/build run: | @@ -79,13 +61,20 @@ jobs: working-directory: bindings/c/build if: ${{ matrix.os == 'ubuntu-latest' }} run: | + sudo apt update sudo apt install -y valgrind valgrind -v --leak-check=full --show-leak-kinds=all tests/test_check valgrind -v --leak-check=full --show-leak-kinds=all tests/test_item valgrind -v --leak-check=full --show-leak-kinds=all tests/test_result valgrind -v --leak-check=full --show-leak-kinds=all tests/test_runner valgrind -v --leak-check=full --show-leak-kinds=all tests/test_status + - name: Run LCov + working-directory: bindings/c/build + run: | + sudo apt update + sudo apt install -y lcov + lcov -c -d . -o coverage.info - uses: codecov/codecov-action@v3 with: - files: lcov.info flags: lang-c + files: build/coverage.info diff --git a/.github/workflows/test_suite_cpp.yml b/.github/workflows/test_suite_cpp.yml new file mode 100644 index 0000000..77a30ff --- /dev/null +++ b/.github/workflows/test_suite_cpp.yml @@ -0,0 +1,76 @@ +name: C++ Test Suite + +on: + push: + paths: + - .github/workflows/*_cpp.yml + - bindings/cpp/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules + pull_request: + paths: + - .github/workflows/*_cpp.yml + - bindings/cpp/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules + +jobs: + test: + name: C++ test + defaults: + run: + working-directory: bindings/cpp + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Make Build Directory + run: mkdir -p build + - name: CMake Configure + working-directory: bindings/cpp/build + run: | + # Make cmocka a static dependency when testing. + cmake ../ -DBUILD_SHARED_LIBS=OFF -DBUILD_CPPCHECKS_TESTS=ON + - name: CMake Build + working-directory: bindings/cpp/build + run: | + cmake --build . --config=Debug + - name: Run Test + working-directory: bindings/cpp/build + run: ctest -C Debug -VV --output-on-failure + - name: Run Valgrind + working-directory: bindings/cpp/build + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt update + sudo apt install -y valgrind + valgrind -v --leak-check=full --show-leak-kinds=all tests/test_check + valgrind -v --leak-check=full --show-leak-kinds=all tests/test_item + valgrind -v --leak-check=full --show-leak-kinds=all tests/test_result + valgrind -v --leak-check=full --show-leak-kinds=all tests/test_runner + valgrind -v --leak-check=full --show-leak-kinds=all tests/test_status + - name: Run LCov + working-directory: bindings/cpp/build + run: | + sudo apt update + sudo apt install -y lcov + lcov -c -d . -o coverage.info + - uses: codecov/codecov-action@v3 + with: + flags: lang-cpp + files: build/coverage.info diff --git a/.github/workflows/test_suite_python.yml b/.github/workflows/test_suite_python.yml index 567979d..8005e7a 100644 --- a/.github/workflows/test_suite_python.yml +++ b/.github/workflows/test_suite_python.yml @@ -2,21 +2,23 @@ name: Python Test Suite on: push: - # paths: - # - .github/workflows/test_suite_python.yml - # - bindings/python/conftest.py - # - bindings/python/pychecks/** - # - bindings/python/pyproject.toml - # - bindings/python/src/** - # - bindings/python/tests/** - # pull_request: - # paths: - # - .github/workflows/test_suite_python.yml - # - bindings/python/conftest.py - # - bindings/python/pychecks/** - # - bindings/python/pyproject.toml - # - bindings/python/src/** - # - bindings/python/tests/** + paths: + - .github/workflows/*_python.yml + - bindings/python/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules + pull_request: + paths: + - .github/workflows/*_python.yml + - bindings/python/** + - src/** + - Cargo.lock + - Cargo.toml + - deny.toml + - .gitmodules jobs: test: diff --git a/.github/workflows/test_suite_rust.yml b/.github/workflows/test_suite_rust.yml index 45e6ba1..fb35f7b 100644 --- a/.github/workflows/test_suite_rust.yml +++ b/.github/workflows/test_suite_rust.yml @@ -3,7 +3,7 @@ name: Rust Test Suite on: push: paths: - - .github/workflows/test_suite_rust.yml + - .github/workflows/*_rust.yml - src/** - tests/** - Cargo.lock @@ -11,7 +11,7 @@ on: - deny.toml pull_request: paths: - - .github/workflows/test_suite_rust.yml + - .github/workflows/*_rust.yml - src/** - tests/** - Cargo.lock diff --git a/.gitmodules b/.gitmodules index 1b0ccab..238018e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "bindings/c/corrosion"] path = bindings/c/corrosion url = https://github.com/corrosion-rs/corrosion.git +[submodule "bindings/cpp/googletest"] + path = bindings/cpp/googletest + url = https://github.com/google/googletest.git diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index b8e2bad..3fb83d7 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -17,10 +17,16 @@ option(CODE_COVERAGE "Enable coverage reporting" OFF) # -------------------------------------------------------------------------------- set(UNIT_TESTING OFF) -enable_testing() - add_subdirectory(corrosion) -corrosion_import_crate(MANIFEST_PATH "${CMAKE_SOURCE_DIR}/Cargo.toml") -add_subdirectory(cmocka) -add_subdirectory(tests) +if (NOT DEFINED CCHECKS_SOURCE_DIR OR CCHECKS_SOURCE_DIR MATCHES "") +set(CCHECKS_SOURCE_DIR "${PROJECT_SOURCE_DIR}") +endif() + +corrosion_import_crate(MANIFEST_PATH "${CCHECKS_SOURCE_DIR}/Cargo.toml") + +if (BUILD_CCHECKS_TESTS) + enable_testing() + add_subdirectory(cmocka) + add_subdirectory(tests) +endif() diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml index 5f69060..922d76c 100644 --- a/bindings/c/Cargo.toml +++ b/bindings/c/Cargo.toml @@ -18,8 +18,8 @@ panic = "abort" [dependencies] checks = { path = "../../" } -bitflags = "2.0.0" -libc = "0.2.138" +bitflags = "2.4.1" +libc = "0.2.152" [build-dependencies] cbindgen = "0.26.0" diff --git a/bindings/c/src/c_string.rs b/bindings/c/src/c_string.rs index f5c1429..67036a6 100644 --- a/bindings/c/src/c_string.rs +++ b/bindings/c/src/c_string.rs @@ -42,7 +42,7 @@ impl CChecksString { pub(crate) fn new>(text: T) -> Self { unsafe extern "C" fn destroy_fn(string: *mut CChecksString) { if !(*string).string.is_null() { - unsafe { CString::from_raw((*string).string) }; + unsafe { drop(CString::from_raw((*string).string)) }; } } diff --git a/bindings/c/src/item.rs b/bindings/c/src/item.rs index 3dd4e0f..0a145c0 100644 --- a/bindings/c/src/item.rs +++ b/bindings/c/src/item.rs @@ -158,7 +158,7 @@ impl std::fmt::Debug for CChecksItem { unsafe { let c_str = CStr::from_ptr(c_display.string); let result = match c_str.to_str() { - Ok(s) => write!(f, "Item({})", s), + Ok(s) => write!(f, "{}", s), Err(_) => Err(std::fmt::Error), }; @@ -260,7 +260,7 @@ pub unsafe extern "C" fn cchecks_item_destroy(item: *mut CChecksItem) { /// The item pointer must not be null. #[no_mangle] pub unsafe extern "C" fn cchecks_item_debug(item: *const CChecksItem) -> crate::CChecksString { - let result = format!("{:?}", (*item)); + let result = format!("Item({:?})", (*item)); crate::CChecksString::new(result) } diff --git a/bindings/c/tests/CMakeLists.txt b/bindings/c/tests/CMakeLists.txt index 5571644..71d0667 100644 --- a/bindings/c/tests/CMakeLists.txt +++ b/bindings/c/tests/CMakeLists.txt @@ -6,6 +6,10 @@ set(TESTS test_status ) +SET(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage") +SET(CMAKE_C_FLAGS "-g -O0 -Wall -W -fprofile-arcs -ftest-coverage") +SET(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage") + if(CODE_COVERAGE AND CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") # Don't use e.g. GNU extension (like -std=gnu++11) for portability set(CMAKE_C_EXTENSIONS OFF) diff --git a/bindings/cpp/.gitignore b/bindings/cpp/.gitignore new file mode 100644 index 0000000..89ee243 --- /dev/null +++ b/bindings/cpp/.gitignore @@ -0,0 +1,46 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +build/ + +# Cmake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt new file mode 100644 index 0000000..2846ff1 --- /dev/null +++ b/bindings/cpp/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.15.0 FATAL_ERROR) + +set(CPPCHECKS_VERSION_MAJOR "0") +set(CPPCHECKS_VERSION_MINOR "1") +set(CPPCHECKS_VERSION_PATCH "0") +set(CPPCHECKS_VERSION ${CPPCHECKS_VERSION_MAJOR}.${CPPCHECKS_VERSION_MINOR}.${CPPCHECKS_VERSION_PATCH}) + +project(cppchecks VERSION ${CPPCHECKS_VERSION} LANGUAGES CXX) + +# -------------------------------------------------------------------------------- +# Build Options +# -------------------------------------------------------------------------------- +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# -------------------------------------------------------------------------------- +# Do the build +# -------------------------------------------------------------------------------- +set(CHECKS_HEADERS + ${PROJECT_SOURCE_DIR}/include/cppchecks/check.h + ${PROJECT_SOURCE_DIR}/include/cppchecks/core.h + ${PROJECT_SOURCE_DIR}/include/cppchecks/item.h + ${PROJECT_SOURCE_DIR}/include/cppchecks/items.h + ${PROJECT_SOURCE_DIR}/include/cppchecks/result.h + ${PROJECT_SOURCE_DIR}/include/cppchecks/runner.h + ${PROJECT_SOURCE_DIR}/include/cppchecks/status.h +) +set(CHECKS_SRC + ${PROJECT_SOURCE_DIR}/src/core.cpp +) + +add_library(${CMAKE_PROJECT_NAME} + ${CHECKS_HEADERS} + ${CHECKS_SRC} +) + +set(CCHECKS_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../c) +add_subdirectory(${PROJECT_SOURCE_DIR}/../c ${CMAKE_BINARY_DIR}/cchecks) + +target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/../c/include +) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) +target_link_libraries(${CMAKE_PROJECT_NAME} cchecks) + +install(FILES ${CHECKS_HEADERS} DESTINATION include/cppchecks) +install(TARGETS ${CMAKE_PROJECT_NAME} EXPORT cppchecks DESTINATION lib) + +if (BUILD_CPPCHECKS_TESTS) + enable_testing() + add_subdirectory(googletest EXCLUDE_FROM_ALL) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + add_subdirectory(tests) +endif() diff --git a/bindings/cpp/googletest b/bindings/cpp/googletest new file mode 160000 index 0000000..356fc30 --- /dev/null +++ b/bindings/cpp/googletest @@ -0,0 +1 @@ +Subproject commit 356fc301251378e0f6fa6aa794d73714202887ac diff --git a/bindings/cpp/include/cppchecks/check.h b/bindings/cpp/include/cppchecks/check.h new file mode 100644 index 0000000..285cf4d --- /dev/null +++ b/bindings/cpp/include/cppchecks/check.h @@ -0,0 +1,82 @@ +#pragma once + +extern "C" +{ +#include +} + +#include +#include +#include + +#include "cppchecks/core.h" +#include "cppchecks/item.h" +#include "cppchecks/items.h" +#include "cppchecks/status.h" +#include "cppchecks/result.h" + +namespace CPPCHECKS_NAMESPACE +{ + enum class CheckHint + { + None = CCHECKS_CHECK_HINT_NONE, + AutoFix = CCHECKS_CHECK_HINT_AUTO_FIX, + }; + + template + class BaseCheck + { + public: + BaseCheck() + { + _check.title_fn = title_fn; + _check.description_fn = description_fn; + _check.hint_fn = hint_fn; + _check.check_fn = check_fn; + _check.auto_fix_fn = auto_fix_fn; + } + + virtual std::string title() = 0; + virtual std::string description() = 0; + virtual CheckHint hint() = 0; + virtual CPPCHECKS_NAMESPACE::CheckResult check() = 0; + virtual std::string auto_fix() { return std::string("Auto fix is not implemented."); } + + private: + CChecksBaseCheck _check; + static const char *title_fn(const CChecksBaseCheck *check) + { + return ((CPPCHECKS_NAMESPACE::BaseCheck *)check)->title().c_str(); + } + + static const char *description_fn(const CChecksBaseCheck *check) + { + return ((CPPCHECKS_NAMESPACE::BaseCheck *)check)->description().c_str(); + } + + static CChecksCheckHint hint_fn(const CChecksBaseCheck *check) + { + return ((CPPCHECKS_NAMESPACE::BaseCheck *)check)->hint(); + } + + static CChecksCheckResult check_fn(const CChecksBaseCheck *check) + { + return ((CPPCHECKS_NAMESPACE::BaseCheck *)check)->check()._result; + } + + static CChecksAutoFixResult auto_fix_fn(const CChecksBaseCheck *check) + { + auto auto_fix_result = ((CPPCHECKS_NAMESPACE::BaseCheck *)check)->auto_fix(); + + if (!auto_fix_result) + { + return cchecks_check_auto_fix_ok(); + } + else + { + return cchecks_check_auto_fix_error(auto_fix_result.value().c_str()); + } + } + }; + +} // namespace CPPCHECKS_NAMESPACE diff --git a/bindings/cpp/include/cppchecks/core.h b/bindings/cpp/include/cppchecks/core.h new file mode 100644 index 0000000..7b06225 --- /dev/null +++ b/bindings/cpp/include/cppchecks/core.h @@ -0,0 +1,3 @@ +#pragma once + +#define CPPCHECKS_NAMESPACE cppchecks diff --git a/bindings/cpp/include/cppchecks/item.h b/bindings/cpp/include/cppchecks/item.h new file mode 100644 index 0000000..2bccec7 --- /dev/null +++ b/bindings/cpp/include/cppchecks/item.h @@ -0,0 +1,163 @@ +#pragma once + +extern "C" +{ +#include +} + +#include +#include +#include +#include + +#include "cppchecks/core.h" + +namespace CPPCHECKS_NAMESPACE +{ + namespace _private + { + void destroy_str_fn(struct CChecksString *str) + { + delete[] str->string; + } + } // namespace _private + + template + class Item : private CChecksItem + { + public: + Item(const T &value, const std::string &type_hint) : _value(value), _type_hint(type_hint) + { + this->init(); + } + + virtual ~Item() {} + + virtual void clone(const Item &other) + { + this->clone(&other); + } + + virtual void clone(const Item *other) + { + this->init(); + this->_value = other->value(); + this->_type_hint = other->type_hint(); + } + + const T &value() const { return this->_value; } + const std::string &type_hint() const { return this->_type_hint; } + + // Display + virtual std::string display() const + { + return std::to_string(this->value()); + } + // Debug + virtual std::string debug() const + { + std::ostringstream stream; + + stream << "Item(" << this->display() << ")"; + + return stream.str(); + } + + // Ordering + virtual inline bool operator<(const Item &other) const { return this->value() < other.value(); } + virtual inline bool operator>(const Item &other) const { return other < *this; } + virtual inline bool operator<=(const Item &other) const { return !(*this > other); } + virtual inline bool operator>=(const Item &other) const { return !(*this < other); } + + // Comparison + virtual inline bool operator==(const Item &other) const { return this->value() == other.value(); } + virtual inline bool operator!=(const Item &other) const { return !(*this == other); } + + private: + T _value; + std::string _type_hint; + + void init() + { + this->type_hint_fn = _type_hint_impl; + this->value_fn = _value_impl; + this->clone_fn = _clone_impl; + this->destroy_fn = _destroy_impl; + this->debug_fn = _debug_impl; + this->display_fn = _display_impl; + this->lt_fn = _lt_impl; + this->eq_fn = _eq_impl; + } + + static const char *_type_hint_impl(const CChecksItem *item) + { + const char *type_hint = ((Item *)item)->type_hint().c_str(); + + if (type_hint == nullptr || std::string_view(type_hint).empty()) + { + return nullptr; + } + + return type_hint; + } + + static const void *_value_impl(const CChecksItem *item) + { + const T *value = &((Item *)item)->value(); + + return (void *)value; + } + + static void _clone_impl(const CChecksItem *item, CChecksItem *other) + { + ((Item *)item)->clone((Item *)other); + } + + static void _destroy_impl(CChecksItem *item) + { + ((Item *)item)->~Item(); + }; + + static CChecksString _debug_impl(const CChecksItem *item) + { + Item *cppitem = (Item *)item; + + std::string msg = cppitem->display(); + + char *cstr = new char[msg.length() + 1]; + std::strcpy(cstr, msg.c_str()); + + CChecksString cchecks_msg; + cchecks_msg.string = cstr; + cchecks_msg.destroy_fn = CPPCHECKS_NAMESPACE::_private::destroy_str_fn; + + return cchecks_msg; + } + + static CChecksString _display_impl(const CChecksItem *item) + { + Item *cppitem = (Item *)item; + + std::string msg = cppitem->display(); + + char *cstr = new char[msg.length() + 1]; + std::strcpy(cstr, msg.c_str()); + + CChecksString cchecks_msg; + cchecks_msg.string = cstr; + cchecks_msg.destroy_fn = CPPCHECKS_NAMESPACE::_private::destroy_str_fn; + + return cchecks_msg; + } + + static bool _lt_impl(const CChecksItem *item, const CChecksItem *other) + { + return (*(Item *)item) < (*(Item *)other); + } + + static bool _eq_impl(const CChecksItem *item, const CChecksItem *other) + { + return (*(Item *)item) == (*(Item *)other); + } + }; +} // namespace CPPCHECKS_NAMESPACE diff --git a/bindings/cpp/include/cppchecks/items.h b/bindings/cpp/include/cppchecks/items.h new file mode 100644 index 0000000..c13c626 --- /dev/null +++ b/bindings/cpp/include/cppchecks/items.h @@ -0,0 +1,87 @@ +#pragma once + +extern "C" +{ +#include +} + +#include +#include +#include + +#include "cppchecks/core.h" +#include "cppchecks/item.h" + +namespace CPPCHECKS_NAMESPACE +{ + template + class Items + { + public: + class iterator + { + public: + iterator(const CPPCHECKS_NAMESPACE::Item *items) : _items(items) {} + iterator &operator++() + { + this->_items++; + return *this; + } + iterator operator++(int) + { + iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(iterator &other) const + { + return this->_items == other._items; + } + + bool operator!=(iterator &other) const + { + return !(*this == other); + } + + const CPPCHECKS_NAMESPACE::Item &operator*() + { + return *_items; + } + + // iterator traits + using difference_type = std::ptrdiff_t; + // using difference_type = size_t; + using value_type = CPPCHECKS_NAMESPACE::Item; + using pointer = const CPPCHECKS_NAMESPACE::Item *; + using reference = const CPPCHECKS_NAMESPACE::Item &; + using iterator_category = std::input_iterator_tag; + + private: + const CPPCHECKS_NAMESPACE::Item *_items; + }; + + Items(const CPPCHECKS_NAMESPACE::Item *items, size_t count) : _items(items), _count(count) {} + + const CPPCHECKS_NAMESPACE::Item operator[](size_t index) const + { + return _items[index]; + } + + size_t size() const noexcept { return _count; } + + iterator begin() + { + return Items::iterator(_items); + } + + iterator end() + { + return Items::iterator(_items + _count); + } + + private: + const CPPCHECKS_NAMESPACE::Item *_items; + size_t _count; + }; +} // namespace CPPCHECKS_NAMESPACE diff --git a/bindings/cpp/include/cppchecks/result.h b/bindings/cpp/include/cppchecks/result.h new file mode 100644 index 0000000..ca8a98f --- /dev/null +++ b/bindings/cpp/include/cppchecks/result.h @@ -0,0 +1,128 @@ +#pragma once + +extern "C" +{ +#include +} + +#include +#include +#include +#include +#include + +#include "cppchecks/core.h" +#include "cppchecks/item.h" +#include "cppchecks/items.h" +#include "cppchecks/status.h" + +namespace CPPCHECKS_NAMESPACE +{ + template + class CheckResult + { + public: + CheckResult(CPPCHECKS_NAMESPACE::Status status, const std::string &message, const std::vector> &items, bool can_fix, bool can_skip, std::string error) : _items(items) + { + CChecksStatus cstatus = status.c_status(); + const char *cmessage = message.c_str(); + size_t item_size = sizeof(CPPCHECKS_NAMESPACE::Item); + size_t item_count = items.size(); + const char *cerror = error.c_str(); + + _result = cchecks_check_result_new(cstatus, cmessage, (CChecksItem *)_items.data(), item_size, item_count, can_fix, can_skip, cerror, items_destroy_fn); + } + + static CheckResult passed(const std::string &message, const std::vector> &items, bool can_fix, bool can_skip) + { + return CheckResult{CPPCHECKS_NAMESPACE::Status::Passed, message, items, can_fix, can_skip, ""}; + } + + static CheckResult skipped(const std::string &message, const std::vector> &items, bool can_fix, bool can_skip) + { + return CheckResult{CPPCHECKS_NAMESPACE::Status::Skipped, message, items, can_fix, can_skip, ""}; + } + + static CheckResult warning(const std::string &message, const std::vector> &items, bool can_fix, bool can_skip) + { + return CheckResult{CPPCHECKS_NAMESPACE::Status::Warning, message, items, can_fix, can_skip, ""}; + } + + static CheckResult failed(const std::string &message, const std::vector> &items, bool can_fix, bool can_skip) + { + return CheckResult{CPPCHECKS_NAMESPACE::Status::Failed, message, items, can_fix, can_skip, ""}; + } + + virtual ~CheckResult() + { + // TODO: This causes double frees. + // cchecks_check_result_destroy(&this->_result); + } + + const CPPCHECKS_NAMESPACE::Status status() const + { + return CPPCHECKS_NAMESPACE::Status(cchecks_check_result_status((CChecksCheckResult *)&_result)); + } + + std::string_view message() const + { + return std::string_view(cchecks_check_result_message(&_result).string); + } + + CPPCHECKS_NAMESPACE::Items items() const + { + const CChecksItems *citems = cchecks_check_result_items(&_result); + + const CChecksItem *ptr = citems->ptr; + auto items = CPPCHECKS_NAMESPACE::Items((const CPPCHECKS_NAMESPACE::Item *)ptr, citems->length); + return items; + } + + bool can_fix() const + { + return cchecks_check_result_can_fix(&_result); + } + + bool can_skip() const + { + return cchecks_check_result_can_skip(&_result); + } + + std::string error() const + { + CChecksStringView cerr = cchecks_check_result_error(&_result); + + if (!cerr.string) + { + return std::string(); + } + else + { + return std::string(cerr.string); + } + } + + double check_duration() const + { + return cchecks_check_result_check_duration(&_result); + } + + double fix_duration() const + { + return cchecks_check_result_fix_duration(&_result); + } + + private: + CheckResult() {} + CheckResult(CChecksCheckResult result) : _result(result) {} + CChecksCheckResult _result; + std::vector> _items; + + static void items_destroy_fn(CChecksItem *item) + { + // Do not destroy the items, since C++ will handle the destruction. + // The items are owned by the class itself, and the C result type references it. + } + }; + +} // namespace CPPCHECKS_NAMESPACE diff --git a/bindings/cpp/include/cppchecks/runner.h b/bindings/cpp/include/cppchecks/runner.h new file mode 100644 index 0000000..096a44e --- /dev/null +++ b/bindings/cpp/include/cppchecks/runner.h @@ -0,0 +1,28 @@ +#pragma once + +extern "C" +{ +#include +} + +#include +#include + +#include "cppchecks/core.h" +#include "cppchecks/check.h" +#include "cppchecks/result.h" + +namespace CPPCHECKS_NAMESPACE +{ + template + CPPCHECKS_NAMESPACE::CheckResult run(const CPPCHECKS_NAMESPACE::BaseCheck &check) + { + CPPCHECKS_NAMESPACE::CheckResult(cchecks_run(&check._check)); + } + + template + CPPCHECKS_NAMESPACE::CheckResult auto_fix(CPPCHECKS_NAMESPACE::BaseCheck &check) + { + CPPCHECKS_NAMESPACE::CheckResult(cchecks_auto_fix(&check._check)); + } +} // namespace CPPCHECKS_NAMESPACE diff --git a/bindings/cpp/include/cppchecks/status.h b/bindings/cpp/include/cppchecks/status.h new file mode 100644 index 0000000..7fa6f21 --- /dev/null +++ b/bindings/cpp/include/cppchecks/status.h @@ -0,0 +1,71 @@ +#pragma once + +extern "C" +{ +#include +} + +#include "cppchecks/core.h" + +namespace CPPCHECKS_NAMESPACE +{ + class Status + { + public: + enum Value + { + Pending = CChecksStatusPending, + Skipped = CChecksStatusSkipped, + Passed = CChecksStatusPassed, + Warning = CChecksStatusWarning, + Failed = CChecksStatusFailed, + SystemError = CChecksStatusSystemError, + }; + + Status() = default; + constexpr Status(Value status) : _value(status) {} + constexpr Status(CChecksStatus status) : _value((Status::Value)status) {} + + constexpr operator Value() const { return _value; } + explicit operator bool() const = delete; + + bool is_pending() const + { + return cchecks_status_is_pending((CChecksStatus *)&_value); + } + + bool has_passed() const + { + return cchecks_status_has_passed((CChecksStatus *)&_value); + } + + bool has_failed() const + { + return cchecks_status_has_failed((CChecksStatus *)&_value); + } + + CChecksStatus c_status() const + { + switch (_value) + { + case Status::Value::Pending: + return CChecksStatusPending; + case Status::Value::Skipped: + return CChecksStatusSkipped; + case Status::Value::Passed: + return CChecksStatusPassed; + case Status::Value::Warning: + return CChecksStatusWarning; + case Status::Value::Failed: + return CChecksStatusFailed; + case Status::Value::SystemError: + return CChecksStatusSystemError; + default: + return CChecksStatusSystemError; + } + } + + private: + Value _value; + }; +} // namespace CPPCHECKS_NAMESPACE diff --git a/bindings/cpp/src/core.cpp b/bindings/cpp/src/core.cpp new file mode 100644 index 0000000..758f84a --- /dev/null +++ b/bindings/cpp/src/core.cpp @@ -0,0 +1,10 @@ +extern "C" +{ +#include +} + +#include "cppchecks/check.h" +#include "cppchecks/core.h" +#include "cppchecks/item.h" +#include "cppchecks/items.h" +#include "cppchecks/status.h" diff --git a/bindings/cpp/tests/CMakeLists.txt b/bindings/cpp/tests/CMakeLists.txt new file mode 100644 index 0000000..8926f27 --- /dev/null +++ b/bindings/cpp/tests/CMakeLists.txt @@ -0,0 +1,32 @@ +set(TESTS + test_check + test_item + test_items + test_result + test_runner + test_status +) +set(TEST_HEADERS + item_impl.h +) + +SET(CMAKE_CXX_FLAGS "-g -O0 -Wall -fprofile-arcs -ftest-coverage") +SET(CMAKE_C_FLAGS "-g -O0 -Wall -W -fprofile-arcs -ftest-coverage") +SET(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage") + +foreach(TEST ${TESTS}) + add_executable(${TEST} ${TEST}.cpp + ${TEST_HEADERS} + ) + target_include_directories(${TEST} PRIVATE + "${PROJECT_SOURCE_DIR}/tests" + "${PROJECT_SOURCE_DIR}/include" + "${PROJECT_SOURCE_DIR}/googletest/googletest/include" + ) + target_link_libraries(${TEST} + PUBLIC + gtest_main + cppchecks + ) + add_test(NAME ${TEST} COMMAND ${TEST}) +endforeach() diff --git a/bindings/cpp/tests/item_impl.h b/bindings/cpp/tests/item_impl.h new file mode 100644 index 0000000..4861328 --- /dev/null +++ b/bindings/cpp/tests/item_impl.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include + +using IntItem = CPPCHECKS_NAMESPACE::Item; diff --git a/bindings/cpp/tests/test_check.cpp b/bindings/cpp/tests/test_check.cpp new file mode 100644 index 0000000..7ce7191 --- /dev/null +++ b/bindings/cpp/tests/test_check.cpp @@ -0,0 +1,7 @@ +#include +#include + +TEST(Check, ItWorks) +{ + ASSERT_EQ(1, 1); +} diff --git a/bindings/cpp/tests/test_item.cpp b/bindings/cpp/tests/test_item.cpp new file mode 100644 index 0000000..1891a50 --- /dev/null +++ b/bindings/cpp/tests/test_item.cpp @@ -0,0 +1,219 @@ +extern "C" +{ +#include +} + +#include +#include +#include +#include + +#include "item_impl.h" + +TEST(Item, IntItemValueSuccess) +{ + IntItem item{1, std::string("test")}; + ASSERT_EQ(item.value(), 1); +} + +TEST(Item, IntItemTypeHintSuccess) +{ + IntItem item{1, std::string("test")}; + ASSERT_EQ(item.type_hint(), std::string("test")); + + item = IntItem{1, ""}; + ASSERT_EQ(item.type_hint(), ""); +} + +TEST(Item, IntItemDisplaySuccess) +{ + IntItem item{1, std::string("test")}; + + ASSERT_EQ(item.display(), "1"); +} + +TEST(Item, IntItemDebugSuccess) +{ + IntItem item{1, std::string("test")}; + + ASSERT_EQ(item.debug(), "Item(1)"); +} + +TEST(Item, IntItemCloneSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{2, std::string("other")}; + other.clone(item); + + ASSERT_EQ(item.value(), other.value()); + ASSERT_EQ(item.type_hint(), other.type_hint()); + ASSERT_NE(&item, &other); +} + +TEST(Item, IntItemLtSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{2, std::string("test")}; + + ASSERT_LT(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_FALSE(item < other); +} + +TEST(Item, IntItemLeSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{2, std::string("test")}; + + ASSERT_LE(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_LE(item, other); + + item = IntItem{2, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_FALSE(item <= other); +} + +TEST(Item, IntItemGtSuccess) +{ + IntItem item{2, std::string("test")}; + IntItem other{1, std::string("test")}; + + ASSERT_GT(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_FALSE(item > other); +} + +TEST(Item, IntItemGeSuccess) +{ + IntItem item{2, std::string("test")}; + IntItem other{1, std::string("test")}; + + ASSERT_GE(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_GE(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{2, std::string("test")}; + + ASSERT_FALSE(item >= other); +} + +TEST(Item, IntItemEqSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{1, std::string("test")}; + + ASSERT_EQ(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{2, std::string("test")}; + + ASSERT_FALSE(item == other); +} + +TEST(Item, IntItemNeSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{2, std::string("test")}; + + ASSERT_NE(item, other); + + item = IntItem{1, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_FALSE(item != other); +} + +TEST(Item, CIntItemValueSuccess) +{ + IntItem item{1, std::string("test")}; + ASSERT_EQ(*(int *)cchecks_item_value((CChecksItem *)&item), 1); +} + +TEST(Item, CIntItemTypeHintSuccess) +{ + IntItem item{1, std::string("test")}; + CChecksItem *citem = (CChecksItem *)&item; + const char *ctype_hint = cchecks_item_type_hint(citem); + + ASSERT_STREQ(ctype_hint, "test"); + + item = IntItem{1, ""}; + citem = (CChecksItem *)&item; + ctype_hint = cchecks_item_type_hint(citem); + + ASSERT_EQ(ctype_hint, nullptr); +} + +TEST(Item, CIntItemDisplaySuccess) +{ + IntItem item{1, std::string("test")}; + CChecksItem *citem = (CChecksItem *)&item; + CChecksString display = cchecks_item_display(citem); + + ASSERT_STREQ(display.string, "1"); + + cchecks_string_destroy(&display); +} + +TEST(Item, CIntItemDebugSuccess) +{ + IntItem item{1, std::string("test")}; + CChecksItem *citem = (CChecksItem *)&item; + CChecksString debug = cchecks_item_debug(citem); + + ASSERT_STREQ(debug.string, "Item(1)"); + + cchecks_string_destroy(&debug); +} + +TEST(Item, CIntItemCloneSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{2, std::string("other")}; + cchecks_item_clone((CChecksItem *)&item, (CChecksItem *)&other); + + ASSERT_EQ(item.value(), other.value()); + ASSERT_EQ(item.type_hint(), other.type_hint()); + ASSERT_NE(&item, &other); +} + +TEST(Item, CIntItemLtSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{2, std::string("test")}; + + ASSERT_TRUE(cchecks_item_lt((CChecksItem *)&item, (CChecksItem *)&other)); + + item = IntItem{1, std::string("test")}; + other = IntItem{1, std::string("test")}; + + ASSERT_FALSE(cchecks_item_lt((CChecksItem *)&item, (CChecksItem *)&other)); +} + +TEST(Item, CIntItemEqSuccess) +{ + IntItem item{1, std::string("test")}; + IntItem other{1, std::string("test")}; + + ASSERT_TRUE(cchecks_item_eq((CChecksItem *)&item, (CChecksItem *)&other)); + + item = IntItem{1, std::string("test")}; + other = IntItem{2, std::string("test")}; + + ASSERT_FALSE(cchecks_item_eq((CChecksItem *)&item, (CChecksItem *)&other)); +} diff --git a/bindings/cpp/tests/test_items.cpp b/bindings/cpp/tests/test_items.cpp new file mode 100644 index 0000000..774d76b --- /dev/null +++ b/bindings/cpp/tests/test_items.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +#include "item_impl.h" + +TEST(CheckItems, IterateSuccess) +{ + std::vector vec{IntItem(0, ""), IntItem(1, ""), IntItem(2, "")}; + CPPCHECKS_NAMESPACE::Items items{vec.data(), vec.size()}; + int index = 0; + + for (auto &&item : items) + { + ASSERT_EQ(item.value(), index); + index++; + } + + ASSERT_EQ(index, vec.size()); +} diff --git a/bindings/cpp/tests/test_result.cpp b/bindings/cpp/tests/test_result.cpp new file mode 100644 index 0000000..7269ecf --- /dev/null +++ b/bindings/cpp/tests/test_result.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "item_impl.h" + +using IntResult = CPPCHECKS_NAMESPACE::CheckResult; + +void validate_result(IntResult &result, + CPPCHECKS_NAMESPACE::Status &status, + std::string &message, + std::vector &items, + bool can_fix, + bool can_skip, + std::string &error) +{ + EXPECT_EQ(result.status(), status); + EXPECT_EQ(result.message(), message); + + auto result_items = result.items(); + + int index = 0; + + for (auto &&item : result_items) + { + EXPECT_EQ(item.value(), index); + index++; + } + + EXPECT_EQ(index, items.size()); + + if (status == CPPCHECKS_NAMESPACE::Status::SystemError) + { + EXPECT_FALSE(result.can_fix()); + EXPECT_FALSE(result.can_skip()); + } + else + { + EXPECT_EQ(result.can_fix(), can_fix); + EXPECT_EQ(result.can_skip(), can_skip); + } + + EXPECT_EQ(result.error(), error); +} + +class ResultParameterizedTestFixture : public ::testing::TestWithParam, bool, bool, std::string>> +{ +}; + +TEST_P(ResultParameterizedTestFixture, ResultSuccess) +{ + CPPCHECKS_NAMESPACE::Status status = std::get<0>(GetParam()); + std::string message = std::get<1>(GetParam()); + std::vector items = std::get<2>(GetParam()); + bool can_fix = std::get<3>(GetParam()); + bool can_skip = std::get<4>(GetParam()); + std::string error = std::get<5>(GetParam()); + + IntResult result{status, message, items, can_fix, can_skip, error}; + + validate_result(result, status, message, items, can_fix, can_skip, error); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P( + CheckResult, + ResultParameterizedTestFixture, + ::testing::Combine( + ::testing::Values(CPPCHECKS_NAMESPACE::Status::Pending, CPPCHECKS_NAMESPACE::Status::Skipped, CPPCHECKS_NAMESPACE::Status::Passed, CPPCHECKS_NAMESPACE::Status::Warning, CPPCHECKS_NAMESPACE::Status::Failed, CPPCHECKS_NAMESPACE::Status::SystemError), + ::testing::Values(std::string("message")), + ::testing::Values(std::vector{IntItem(0, ""), IntItem(1, ""), IntItem(2, "")}), + ::testing::Bool(), + ::testing::Bool(), + ::testing::Values(std::string("error")) + ) +); +// clang-format on + +class PassedResultParameterizedTestFixture : public ::testing::TestWithParam, bool, bool>> +{ +}; + +TEST_P(PassedResultParameterizedTestFixture, ResultPassedSuccess) +{ + CPPCHECKS_NAMESPACE::Status status = CPPCHECKS_NAMESPACE::Status::Passed; + std::string message = std::get<0>(GetParam()); + std::vector items = std::get<1>(GetParam()); + bool can_fix = std::get<2>(GetParam()); + bool can_skip = std::get<3>(GetParam()); + std::string error = ""; + + IntResult result = IntResult::passed(message, items, can_fix, can_skip); + + validate_result(result, status, message, items, can_fix, can_skip, error); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P( + CheckResult, + PassedResultParameterizedTestFixture, + ::testing::Combine( + ::testing::Values(std::string("message")), + ::testing::Values(std::vector{IntItem(0, ""), IntItem(1, ""), IntItem(2, "")}), + ::testing::Bool(), + ::testing::Bool() + ) +); +// clang-format on + +class SkippedResultParameterizedTestFixture : public ::testing::TestWithParam, bool, bool>> +{ +}; + +TEST_P(SkippedResultParameterizedTestFixture, ResultSkippedSuccess) +{ + CPPCHECKS_NAMESPACE::Status status = CPPCHECKS_NAMESPACE::Status::Skipped; + std::string message = std::get<0>(GetParam()); + std::vector items = std::get<1>(GetParam()); + bool can_fix = std::get<2>(GetParam()); + bool can_skip = std::get<3>(GetParam()); + std::string error = ""; + + IntResult result = IntResult::skipped(message, items, can_fix, can_skip); + + validate_result(result, status, message, items, can_fix, can_skip, error); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P( + CheckResult, + SkippedResultParameterizedTestFixture, + ::testing::Combine( + ::testing::Values(std::string("message")), + ::testing::Values(std::vector{IntItem(0, ""), IntItem(1, ""), IntItem(2, "")}), + ::testing::Bool(), + ::testing::Bool() + ) +); +// clang-format on + +class WarningResultParameterizedTestFixture : public ::testing::TestWithParam, bool, bool>> +{ +}; + +TEST_P(WarningResultParameterizedTestFixture, ResultWarningSuccess) +{ + CPPCHECKS_NAMESPACE::Status status = CPPCHECKS_NAMESPACE::Status::Warning; + std::string message = std::get<0>(GetParam()); + std::vector items = std::get<1>(GetParam()); + bool can_fix = std::get<2>(GetParam()); + bool can_skip = std::get<3>(GetParam()); + std::string error = ""; + + IntResult result = IntResult::warning(message, items, can_fix, can_skip); + + validate_result(result, status, message, items, can_fix, can_skip, error); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P( + CheckResult, + WarningResultParameterizedTestFixture, + ::testing::Combine( + ::testing::Values(std::string("message")), + ::testing::Values(std::vector{IntItem(0, ""), IntItem(1, ""), IntItem(2, "")}), + ::testing::Bool(), + ::testing::Bool() + ) +); +// clang-format on + +class FailedResultParameterizedTestFixture : public ::testing::TestWithParam, bool, bool>> +{ +}; + +TEST_P(FailedResultParameterizedTestFixture, ResultFailedSuccess) +{ + CPPCHECKS_NAMESPACE::Status status = CPPCHECKS_NAMESPACE::Status::Failed; + std::string message = std::get<0>(GetParam()); + std::vector items = std::get<1>(GetParam()); + bool can_fix = std::get<2>(GetParam()); + bool can_skip = std::get<3>(GetParam()); + std::string error = ""; + + IntResult result = IntResult::failed(message, items, can_fix, can_skip); + + validate_result(result, status, message, items, can_fix, can_skip, error); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P( + CheckResult, + FailedResultParameterizedTestFixture, + ::testing::Combine( + ::testing::Values(std::string("message")), + ::testing::Values(std::vector{IntItem(0, ""), IntItem(1, ""), IntItem(2, "")}), + ::testing::Bool(), + ::testing::Bool() + ) +); +// clang-format on diff --git a/bindings/cpp/tests/test_runner.cpp b/bindings/cpp/tests/test_runner.cpp new file mode 100644 index 0000000..09b5242 --- /dev/null +++ b/bindings/cpp/tests/test_runner.cpp @@ -0,0 +1,7 @@ +#include +#include + +TEST(Runner, ItWorks) +{ + ASSERT_EQ(1, 1); +} diff --git a/bindings/cpp/tests/test_status.cpp b/bindings/cpp/tests/test_status.cpp new file mode 100644 index 0000000..83c4bbd --- /dev/null +++ b/bindings/cpp/tests/test_status.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +TEST(Status, IsStatusPendingSuccess) +{ + auto cases = std::vector{ + std::make_tuple(cppchecks::Status::Pending, true), + std::make_tuple(cppchecks::Status::Skipped, false), + std::make_tuple(cppchecks::Status::Passed, false), + std::make_tuple(cppchecks::Status::Warning, false), + std::make_tuple(cppchecks::Status::Failed, false), + std::make_tuple(cppchecks::Status::SystemError, false), + }; + + cppchecks::Status status; + bool expected; + + for (auto &&test_case : cases) + { + status = std::get<0>(test_case); + expected = std::get<1>(test_case); + ASSERT_EQ(status.is_pending(), expected); + } +} + +TEST(Status, HasStatusPassedSuccess) +{ + auto cases = std::vector{ + std::make_tuple(cppchecks::Status::Pending, false), + std::make_tuple(cppchecks::Status::Skipped, false), + std::make_tuple(cppchecks::Status::Passed, true), + std::make_tuple(cppchecks::Status::Warning, true), + std::make_tuple(cppchecks::Status::Failed, false), + std::make_tuple(cppchecks::Status::SystemError, false), + }; + + cppchecks::Status status; + bool expected; + + for (auto &&test_case : cases) + { + status = std::get<0>(test_case); + expected = std::get<1>(test_case); + ASSERT_EQ(status.has_passed(), expected); + } +} + +TEST(Status, HasStatusFailedSuccess) +{ + auto cases = std::vector{ + std::make_tuple(cppchecks::Status::Pending, false), + std::make_tuple(cppchecks::Status::Skipped, false), + std::make_tuple(cppchecks::Status::Passed, false), + std::make_tuple(cppchecks::Status::Warning, false), + std::make_tuple(cppchecks::Status::Failed, true), + std::make_tuple(cppchecks::Status::SystemError, true), + }; + + cppchecks::Status status; + bool expected; + + for (auto &&test_case : cases) + { + status = std::get<0>(test_case); + expected = std::get<1>(test_case); + ASSERT_EQ(status.has_failed(), expected); + } +}