diff --git a/AGENTS.md b/AGENTS.md index d01d22a..b9417f9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,10 +54,10 @@ Current version tag: **5.1.0** (latest tag on the develop branch) | VARIANT value | Define set | Backend | Source path | |---------------|----------------|-----------|---------------------------------| | `C` (default) | `-DUT_CUNIT` | CUnit 2.1-3 | `src/c_source/ut_cunit.c` | -| `CPP` | (none extra) | GTest 1.15.2 | `src/cpp_source/ut_gtest.cpp` | +| `CPP` | (none extra) | GTest + GMock 1.15.2 | `src/cpp_source/ut_gtest.cpp` | When `UT_CUNIT` is defined, `ut.h` includes `ut_cunit.h`. -When `UT_CUNIT` is **not** defined (C++ path), `ut.h` includes `ut_gtest.h`. +When `UT_CUNIT` is **not** defined (C++ path), `ut.h` includes `ut_gtest.h` **and** `ut_gmock.h` (GoogleMock wrappers). The combined googletest distribution ships googlemock, so no extra download is needed. --- @@ -165,7 +165,7 @@ All `_FATAL` macros record failure and **abort the current test**. --- -## 5. C++ Path API (ut_gtest.h) -- When VARIANT=CPP +## 5. C++ Path API (ut_gtest.h + ut_gmock.h) -- When VARIANT=CPP ### Test fixture class @@ -238,6 +238,25 @@ Non-`_FATAL` use GTest `EXPECT_*` (continue on failure). > Note: the C++ path does **not** define the `_MSG`/`_LOG` macro family or the `UT_ASSERT_PTR_*` macros from the C path, and has no plain `UT_ASSERT` (use `UT_ASSERT_TRUE`). Conversely, `UT_ASSERT_LESS`, `UT_ASSERT_GREATER`, the throw macros, and the floating-point and ignore-case string macros exist only in the C++ path. +### Mocking macros (C++ path -- GoogleMock backend) + +`ut_gmock.h` wraps GoogleMock so C++ tests can mock an interface under test using `UT_`-prefixed macros, without including `` directly. It is pulled in automatically by `ut.h` on the C++ path. Mock verification is active because the test runner calls `::testing::InitGoogleMock` — an unmet `UT_MOCK_EXPECT_CALL` fails the run. + +| Macro | Maps to | Purpose | +|---|---|---| +| `UT_MOCK_METHOD(ret, name, (args), (specs))` | `MOCK_METHOD` | Declare a mocked method in a mock class | +| `UT_MOCK_EXPECT_CALL(mock, call)` | `EXPECT_CALL` | Set an expectation (chain `.Times()`, `.WillOnce()`, ...) | +| `UT_MOCK_ON_CALL(mock, call)` | `ON_CALL` | Set default behaviour without a count expectation | +| `UT_MOCK_NICE/UT_MOCK_NAGGY/UT_MOCK_STRICT(type)` | `NiceMock/NaggyMock/StrictMock` | Control uninteresting-call strictness | +| `UT_MOCK_ANY`, `UT_MOCK_EQ/NE/GT/GE/LT/LE(v)`, `UT_MOCK_NOTNULL`, `UT_MOCK_ISNULL`, `UT_MOCK_STR_EQ(v)`, `UT_MOCK_BETWEEN(lo,hi)` | `::testing::_`, `Eq/Ne/Gt/...`, matchers | Argument matchers | +| `UT_MOCK_RETURN(v)`, `UT_MOCK_RETURN_REF(v)`, `UT_MOCK_DO_DEFAULT`, `UT_MOCK_INVOKE(f)`, `UT_MOCK_SET_ARG_POINTEE(N,v)`, `UT_MOCK_DO_ALL(...)`, `UT_MOCK_THROW(e)` | `::testing::Return/Invoke/...` | Actions | +| `UT_MOCK_AT_LEAST(n)`, `UT_MOCK_AT_MOST(n)`, `UT_MOCK_EXACTLY(n)`, `UT_MOCK_ANY_NUMBER` | `::testing::AtLeast/...` | Cardinalities (argument to `.Times()`) | +| `UT_MOCK_VERIFY_AND_CLEAR(mock)` | `Mock::VerifyAndClearExpectations` | Verify expectations mid-test | + +Mock classes register and run like any other gtest suite (`UT_ADD_TEST_TO_GROUP` / `UT_ADD_TEST`). See `tests/src/cpp_source/ut_test_gmock.cpp` for a worked interface-mock example. + +**Autogeneration.** `scripts/autogenerate_gmock.sh -f [-c ] [-o ]` parses the pure-virtual methods of a C++ interface header and emits a matching mock (`mock_.h`, one `UT_MOCK_METHOD` per virtual) plus a gtest test skeleton (`test_.cpp`). It handles standard single-line `virtual ... = 0;` declarations; wrap comma-bearing template return types in a typedef. (GoogleMock's own `gmock_gen.py` was removed from googletest by 1.15.2, so generation is provided by ut-core.) + --- ## 6. KVP Profile System (ut_kvp_profile.h) diff --git a/Makefile b/Makefile index 7d2a256..00e8f9d 100755 --- a/Makefile +++ b/Makefile @@ -67,9 +67,12 @@ else # GTEST case SRC_DIRS += $(UT_CORE_DIR)/src EXCLUDE_DIRS = $(SRCDIR)/c_source GTEST_SRC = $(FRAMEWORK_DIR)/gtest/$(TARGET)/googletest-1.15.2 - INC_DIRS += $(GTEST_SRC)/googletest/include $(UT_CORE_DIR)/src/cpp_source $(UT_CORE_DIR)/src + INC_DIRS += $(GTEST_SRC)/googletest/include $(GTEST_SRC)/googlemock/include $(UT_CORE_DIR)/src/cpp_source $(UT_CORE_DIR)/src TEST_LIB_DIR = $(UT_CORE_DIR)/build/$(TARGET)/cpp_libs/lib/ - XLDFLAGS += $(YLDFLAGS) $(LDFLAGS) -L$(UT_CONTROL)/build/$(TARGET)/lib -L$(TEST_LIB_DIR) -lgtest_main -lgtest -lut_control -lpthread -lm + # Link gmock_main (not gtest_main) as the fallback main() so a downstream + # project without its own main() still gets InitGoogleMock (gmock flags + + # verification), matching the init performed in UTTestRunner. + XLDFLAGS += $(YLDFLAGS) $(LDFLAGS) -L$(UT_CONTROL)/build/$(TARGET)/lib -L$(TEST_LIB_DIR) -lgmock_main -lgmock -lgtest -lut_control -lpthread -lm # Source files SRCS := $(shell find $(SRC_DIRS) -type f \( -name '*.cpp' -o -name '*.c' \) | grep -v "$(EXCLUDE_DIRS)") diff --git a/README.md b/README.md index 9fa6053..dfbd3c8 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,8 @@ make VARIANT=CPP This will build the following directories `src/*.c`, in addition to core functions from `ut-core/src/cpp_source` and linking against libraries in `ut-core/framework` +The CPP variant links both GoogleTest and **GoogleMock** (both ship in the pinned googletest distribution). Tests can mock a C++ interface using the `UT_MOCK_METHOD` / `UT_MOCK_EXPECT_CALL` wrappers in `include/ut_gmock.h` (pulled in automatically by `ut.h`); see `tests/src/cpp_source/ut_test_gmock.cpp` for a worked example. + `skeletons/src` - will be included in the linux build to enable stubs to compile against ### Build the target `arm` environment with CPP language diff --git a/include/ut.h b/include/ut.h index 24f80d0..13d99c3 100644 --- a/include/ut.h +++ b/include/ut.h @@ -201,6 +201,7 @@ void UT_regsiter_test_cleanup_function( UT_test_suite_t *pSuite, UT_TestCleanupF #else #include +#include #endif diff --git a/include/ut_gmock.h b/include/ut_gmock.h new file mode 100644 index 0000000..3a1e1b1 --- /dev/null +++ b/include/ut_gmock.h @@ -0,0 +1,163 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** @brief + * UT Unit wrapper to the GoogleMock (gmock) framework. + * + * Hides the functionality of gmock behind UT_-prefixed macros, so tests can + * create and use mocks of C++ interfaces without including + * directly. Companion to ut_gtest.h. + */ +/** @addtogroup UT_GMOCK + * @{ + */ + +#ifndef __UT_GMOCK_H +#define __UT_GMOCK_H + +#include + +/** + * @brief Declares a mocked method inside a mock class. + * + * Thin wrapper over gmock's MOCK_METHOD. Use the modern 3- or 4-argument form: + * UT_MOCK_METHOD(return_type, method_name, (args...)) + * UT_MOCK_METHOD(return_type, method_name, (args...), (const, override)) + * + * @code + * class MockDriver : public IDriver { + * public: + * UT_MOCK_METHOD(int, open, (const char *path), (override)); + * UT_MOCK_METHOD(bool, read, (int fd, void *buf, size_t len), (override)); + * }; + * @endcode + */ +#define UT_MOCK_METHOD(...) MOCK_METHOD(__VA_ARGS__) + +/** + * @brief Sets an expectation on a mock method call. + * + * Wrapper over gmock's EXPECT_CALL. Chain the usual clauses + * (.Times(), .WillOnce(), .WillRepeatedly(), .With(), ...). + * + * @code + * UT_MOCK_EXPECT_CALL(mock, open(UT_MOCK_ANY)).Times(UT_MOCK_AT_LEAST(1)).WillOnce(UT_MOCK_RETURN(3)); + * @endcode + */ +#define UT_MOCK_EXPECT_CALL(mock_object, call) EXPECT_CALL(mock_object, call) + +/** + * @brief Sets the default behaviour of a mock method (no expectation on count). + */ +#define UT_MOCK_ON_CALL(mock_object, call) ON_CALL(mock_object, call) + +/* ---- Mock strictness wrappers ------------------------------------------- */ + +/** + * @brief A mock whose uninteresting calls are silently ignored. + */ +#define UT_MOCK_NICE(type) ::testing::NiceMock + +/** + * @brief A mock whose uninteresting calls produce a warning (gmock default). + */ +#define UT_MOCK_NAGGY(type) ::testing::NaggyMock + +/** + * @brief A mock whose uninteresting calls are treated as failures. + */ +#define UT_MOCK_STRICT(type) ::testing::StrictMock + +/* ---- Common matchers (argument matching in EXPECT_CALL) ------------------ */ + +/* + * NOTE: these UT_MOCK_* matchers are NOT the UT_ASSERT_* assertions from + * ut_gtest.h. A matcher (e.g. UT_MOCK_LT(5)) describes which argument values + * satisfy an expectation and is used *inside* + * UT_MOCK_EXPECT_CALL(mock, foo(UT_MOCK_LT(5))). An assertion (e.g. + * UT_ASSERT_LESS(a, b)) checks a value and records pass/fail. The UT_MOCK_ + * prefix makes the distinction explicit: everything in this header is UT_MOCK_*. + */ + +/** @brief Matches any argument value. */ +#define UT_MOCK_ANY ::testing::_ +/** @brief Matches an argument equal to @p value. */ +#define UT_MOCK_EQ(value) ::testing::Eq(value) +/** @brief Matches an argument not equal to @p value. */ +#define UT_MOCK_NE(value) ::testing::Ne(value) +/** @brief Matches an argument greater than @p value. */ +#define UT_MOCK_GT(value) ::testing::Gt(value) +/** @brief Matches an argument greater than or equal to @p value. */ +#define UT_MOCK_GE(value) ::testing::Ge(value) +/** @brief Matches an argument less than @p value. */ +#define UT_MOCK_LT(value) ::testing::Lt(value) +/** @brief Matches an argument less than or equal to @p value. */ +#define UT_MOCK_LE(value) ::testing::Le(value) +/** @brief Matches a non-null pointer argument. */ +#define UT_MOCK_NOTNULL ::testing::NotNull() +/** @brief Matches a null pointer argument. */ +#define UT_MOCK_ISNULL ::testing::IsNull() +/** @brief Matches a C-string argument equal to @p value. */ +#define UT_MOCK_STR_EQ(value) ::testing::StrEq(value) +/** @brief Matches an argument within [@p lo, @p hi]. */ +#define UT_MOCK_BETWEEN(lo, hi) ::testing::AllOf(::testing::Ge(lo), ::testing::Le(hi)) + +/* ---- Common actions (what a mocked call does) --------------------------- */ + +/** @brief Returns @p value from the mocked call. */ +#define UT_MOCK_RETURN(value) ::testing::Return(value) +/** @brief Returns a reference to @p value from the mocked call. */ +#define UT_MOCK_RETURN_REF(value) ::testing::ReturnRef(value) +/** @brief Performs the method's default action (e.g. the ON_CALL default, or + * gmock's built-in default return for the type). Note: a mocked method with + * no action already returns a default-constructed value automatically. */ +#define UT_MOCK_DO_DEFAULT ::testing::DoDefault() +/** @brief Invokes @p f (a callable) with the mocked call's arguments. */ +#define UT_MOCK_INVOKE(f) ::testing::Invoke(f) +/** @brief Writes @p value through the pointer/reference at argument index @p N. */ +#define UT_MOCK_SET_ARG_POINTEE(N, value) ::testing::SetArgPointee(value) +/** @brief Performs all of the supplied actions in order. */ +#define UT_MOCK_DO_ALL(...) ::testing::DoAll(__VA_ARGS__) +/** @brief Throws @p exception from the mocked call. */ +#define UT_MOCK_THROW(exception) ::testing::Throw(exception) + +/* ---- Cardinalities (arguments to .Times()) ------------------------------ */ + +/** @brief Cardinality: at least @p n calls. */ +#define UT_MOCK_AT_LEAST(n) ::testing::AtLeast(n) +/** @brief Cardinality: at most @p n calls. */ +#define UT_MOCK_AT_MOST(n) ::testing::AtMost(n) +/** @brief Cardinality: exactly @p n calls. */ +#define UT_MOCK_EXACTLY(n) ::testing::Exactly(n) +/** @brief Cardinality: any number of calls (including zero). */ +#define UT_MOCK_ANY_NUMBER ::testing::AnyNumber() + +/** + * @brief Verifies and clears all expectations on @p mock immediately. + * + * Returns true if all expectations were satisfied. Normally verification runs + * automatically when the mock is destroyed (gmock is initialised via + * ::testing::InitGoogleMock in the UT test runner), but this is useful to + * assert expectations mid-test. + */ +#define UT_MOCK_VERIFY_AND_CLEAR(mock) ::testing::Mock::VerifyAndClearExpectations(&(mock)) + +#endif /* UT -> GMOCK - Wrapper */ + +/** @} */ diff --git a/scripts/autogenerate_gmock.sh b/scripts/autogenerate_gmock.sh new file mode 100755 index 0000000..842bb99 --- /dev/null +++ b/scripts/autogenerate_gmock.sh @@ -0,0 +1,243 @@ +#!/usr/bin/env bash + +# * +# * If not stated otherwise in this file or this component's LICENSE file the +# * following copyright and licenses apply: +# * +# * Copyright 2026 RDK Management +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# * + +#* ************************************************************************ +#* +#* ** Project : Unit Test Script +#* ** @addtogroup : ut +#* ** @file : autogenerate_gmock.sh +#* ** @date : 2026 +#* ** +#* ** @brief : Generate a GoogleMock mock class and a gtest test skeleton +#* ** from a C++ interface header, using the ut-core UT_ wrappers +#* ** (ut_gmock.h / ut_gtest.h). CPP/gtest analogue of the C test +#* ** autogeneration. +#* ** +#* ** @note : Parses standard single-line pure-virtual declarations of the +#* ** form: virtual () [const] = 0; +#* ** Multi-line declarations and comma-bearing template return +#* ** types are out of scope (wrap those return types in a typedef). +#* ** +#* ************************************************************************ + +set -euo pipefail + +AGT_SCRIPTS_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGT_COPYRGT_TEMPLATE="${AGT_SCRIPTS_HOME}/templates/AGT_copywrite_template.txt" + +function AGT_gmock_usage() +{ + echo "Usage: ./autogenerate_gmock.sh -f [-c ] [-o ]" + echo " -f Path to the C++ interface header to mock (required)" + echo " -c Interface class name to mock (default: first class in the header)" + echo " -o Output directory (default: ./generated_mocks)" + echo " -h Show this usage" +} + +# Emit the shared Apache copyright header into $1 +function AGT_gmock_copyright() +{ + if [ -f "${AGT_COPYRGT_TEMPLATE}" ]; then + cat "${AGT_COPYRGT_TEMPLATE}" > "$1" + echo "" >> "$1" + fi +} + +# Extract the body (between the outermost { }) of class $2 from header file $1. +# Prints the class body lines to stdout. +function AGT_gmock_class_body() +{ + local header="$1" cls="$2" + awk -v cls="$cls" ' + # Detect the start of the target class: "class " possibly with bases. + !inClass && $0 ~ ("(^|[^[:alnum:]_])class[[:space:]]+" cls "([^[:alnum:]_]|$)") { + inClass = 1 + } + inClass { + # Track brace depth so we stop at the matching closing brace. + n = gsub(/{/, "{"); m = gsub(/}/, "}"); + if (started) print + depth += n - m + if (!started && n > 0) { started = 1 } + if (started && depth <= 0) { exit } + } + ' "$header" +} + +# Print the first class name declared in header $1 (best-effort). +function AGT_gmock_first_class() +{ + grep -oP '(^|[^[:alnum:]_])class\s+\K\w+' "$1" | head -n1 +} + +# Transform one normalised pure-virtual declaration into a UT_MOCK_METHOD line. +# Input (stdin): a single declaration line, e.g. +# virtual int read(const char *name) const = 0; +# Output (stdout): UT_MOCK_METHOD(int, read, (const char *name), (const, override)); +function AGT_gmock_method_line() +{ + local line="$1" specs="(override)" + # Strip comments, leading 'virtual', trailing '= 0;' and surrounding space. + line="$(echo "$line" | sed -E 's://.*$::; s:/\*.*\*/::g')" + line="$(echo "$line" | sed -E 's/^[[:space:]]*virtual[[:space:]]+//; s/[[:space:]]*=[[:space:]]*0[[:space:]]*;[[:space:]]*$//')" + line="$(echo "$line" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')" + + # Params: between the first '(' and the last ')'. + local params="${line#*(}"; params="${params%)*}" + # Everything before the first '(' = return type + method name. + local head="${line%%(*}" + # Trailing qualifiers after the last ')'. + local tail="${line##*)}" + if echo "$tail" | grep -qw const; then + specs="(const, override)" + fi + + head="$(echo "$head" | sed -E 's/[[:space:]]+$//')" + local name="${head##* }" # last whitespace-delimited token + local ret="${head% *}" # everything before it + ret="$(echo "$ret" | sed -E 's/[[:space:]]+$//; s/^[[:space:]]+//')" + + # A pointer/reference return type may bind to the name (e.g. "char *name"); + # migrate any leading '*'/'&' off the name and onto the return type. + local ptr="" + while [ -n "$name" ] && { [ "${name:0:1}" = "*" ] || [ "${name:0:1}" = "&" ]; }; do + ptr="${ptr}${name:0:1}" + name="${name:1}" + done + if [ -n "$ptr" ]; then + ret="${ret} ${ptr}" + fi + + echo " UT_MOCK_METHOD(${ret}, ${name}, (${params}), ${specs});" +} + +AGT_HEADER=""; AGT_CLASS=""; AGT_OUTDIR="./generated_mocks" +while getopts ":f:c:o:h" opt; do + case "$opt" in + f) AGT_HEADER="$OPTARG" ;; + c) AGT_CLASS="$OPTARG" ;; + o) AGT_OUTDIR="$OPTARG" ;; + h) AGT_gmock_usage; exit 0 ;; + *) AGT_gmock_usage; exit 1 ;; + esac +done + +if [ -z "${AGT_HEADER}" ] || [ ! -f "${AGT_HEADER}" ]; then + echo "Error: a valid interface header (-f) is required." >&2 + AGT_gmock_usage + exit 1 +fi + +if [ -z "${AGT_CLASS}" ]; then + AGT_CLASS="$(AGT_gmock_first_class "${AGT_HEADER}")" +fi +if [ -z "${AGT_CLASS}" ]; then + echo "Error: no class found in ${AGT_HEADER}; specify one with -c." >&2 + exit 1 +fi + +# Validate the class name is a plain C++ identifier before using it to form +# file paths and generated code (rejects path components like '/..'). +if ! echo "${AGT_CLASS}" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then + echo "Error: class name '${AGT_CLASS}' is not a valid identifier." >&2 + exit 1 +fi + +mkdir -p "${AGT_OUTDIR}" +HEADER_BASENAME="$(basename "${AGT_HEADER}")" +# Copy the interface header alongside the generated files so the emitted mock +# (which includes it by basename) compiles regardless of where -o points. +if [ "$(cd "$(dirname "${AGT_HEADER}")" && pwd)" != "$(cd "${AGT_OUTDIR}" && pwd)" ]; then + cp "${AGT_HEADER}" "${AGT_OUTDIR}/${HEADER_BASENAME}" +fi +CLASS_LOWER="$(echo "${AGT_CLASS}" | tr '[:upper:]' '[:lower:]')" +MOCK_CLASS="Mock${AGT_CLASS}" +MOCK_HEADER="${AGT_OUTDIR}/mock_${CLASS_LOWER}.h" +TEST_FILE="${AGT_OUTDIR}/test_${CLASS_LOWER}.cpp" + +# Collect the pure-virtual declarations of the target class. Exclude a +# pure-virtual destructor (virtual ~Foo() = 0;) -- destructors cannot be +# expressed with MOCK_METHOD. +mapfile -t PURE_VIRTUALS < <(AGT_gmock_class_body "${AGT_HEADER}" "${AGT_CLASS}" \ + | grep -E 'virtual' | grep -E '=[[:space:]]*0[[:space:]]*;' \ + | grep -vE 'virtual[[:space:]]*~') + +if [ "${#PURE_VIRTUALS[@]}" -eq 0 ]; then + echo "Warning: no pure-virtual methods found in class ${AGT_CLASS}." >&2 +fi + +# ---- Emit the mock header ------------------------------------------------ +AGT_gmock_copyright "${MOCK_HEADER}" +{ + echo "/* Auto-generated GoogleMock for ${AGT_CLASS} (from ${HEADER_BASENAME}). */" + echo "#ifndef MOCK_${CLASS_LOWER^^}_H_" + echo "#define MOCK_${CLASS_LOWER^^}_H_" + echo "" + echo "#include " + echo "#include \"${HEADER_BASENAME}\"" + echo "" + echo "class ${MOCK_CLASS} : public ${AGT_CLASS}" + echo "{" + echo "public:" + for decl in "${PURE_VIRTUALS[@]}"; do + AGT_gmock_method_line "${decl}" + done + echo "};" + echo "" + echo "#endif /* MOCK_${CLASS_LOWER^^}_H_ */" +} >> "${MOCK_HEADER}" + +# ---- Emit the gtest test skeleton --------------------------------------- +AGT_gmock_copyright "${TEST_FILE}" +{ + echo "/* Auto-generated gtest skeleton for ${AGT_CLASS} using ${MOCK_CLASS}. */" + echo "#include " + echo "#include \"mock_${CLASS_LOWER}.h\"" + echo "" + echo "class Test${AGT_CLASS} : public UTCore" + echo "{" + echo "public:" + echo " Test${AGT_CLASS}() : UTCore() {}" + echo " ~Test${AGT_CLASS}() override = default;" + echo "};" + echo "" + echo "UT_ADD_TEST_TO_GROUP(Test${AGT_CLASS}, UT_TESTS_L1)" + echo "" + for decl in "${PURE_VIRTUALS[@]}"; do + # Recover the method name for a per-method test stub. + mline="$(AGT_gmock_method_line "${decl}")" # UT_MOCK_METHOD(ret, name, ...) + mname="$(echo "$mline" | sed -E 's/.*UT_MOCK_METHOD\([^,]*,[[:space:]]*([A-Za-z_][A-Za-z0-9_]*).*/\1/')" + echo "UT_ADD_TEST(Test${AGT_CLASS}, ${mname}_L1)" + echo "{" + echo " ${MOCK_CLASS} mock;" + echo " // TODO: set expectations, e.g." + echo " // UT_MOCK_EXPECT_CALL(mock, ${mname}(UT_MOCK_ANY)).WillOnce(UT_MOCK_RETURN(/* value */));" + echo " // TODO: drive the code under test with 'mock' and assert the result." + echo " (void)mock;" + echo "}" + echo "" + done +} >> "${TEST_FILE}" + +echo "Generated:" +echo " mock : ${MOCK_HEADER}" +echo " test stub: ${TEST_FILE}" +echo " methods : ${#PURE_VIRTUALS[@]} pure-virtual(s) from class ${AGT_CLASS}" diff --git a/scripts/test_autogenerate_script.sh b/scripts/test_autogenerate_script.sh index c8e7b83..b68fd2a 100755 --- a/scripts/test_autogenerate_script.sh +++ b/scripts/test_autogenerate_script.sh @@ -98,6 +98,28 @@ run_command './autogenerate.sh -c' run_command 'echo "y" | ./autogenerate.sh https://github.com/rdkcentral/rdkb-halif-wifi' run_command './autogenerate.sh -c' +#Test 8 : gmock generation from a C++ interface header (self-contained, no network) +AGT_GMOCK_TMP="$(mktemp -d)" +cat > "${AGT_GMOCK_TMP}/ISample.h" <<'IFACE' +#ifndef ISAMPLE_H +#define ISAMPLE_H +#include +class ISample { +public: + virtual ~ISample() = default; + virtual int open(const char *path) = 0; + virtual bool read(int fd, void *buf, size_t len) const = 0; + virtual const char *name() const = 0; +}; +#endif +IFACE +run_command "./autogenerate_gmock.sh -f ${AGT_GMOCK_TMP}/ISample.h -c ISample -o ${AGT_GMOCK_TMP}/out" +# Assert the mock and skeleton were produced with the expected content. +run_command "grep -q 'UT_MOCK_METHOD(int, open, (const char \\*path), (override));' ${AGT_GMOCK_TMP}/out/mock_isample.h" +run_command "grep -q 'UT_MOCK_METHOD(const char \\*, name, (), (const, override));' ${AGT_GMOCK_TMP}/out/mock_isample.h" +run_command "grep -q 'UT_ADD_TEST(TestISample, open_L1)' ${AGT_GMOCK_TMP}/out/test_isample.cpp" +rm -rf "${AGT_GMOCK_TMP}" + # Display consolidated results echo echo "Total tests run: $total_tests" diff --git a/src/cpp_source/ut_gtest.cpp b/src/cpp_source/ut_gtest.cpp index ca09e8a..9b1d515 100644 --- a/src/cpp_source/ut_gtest.cpp +++ b/src/cpp_source/ut_gtest.cpp @@ -92,7 +92,9 @@ class UTTestRunner { int argc = 1; char *argv[1] = {(char *)"test_runner"}; - ::testing::InitGoogleTest(&argc, argv); + // InitGoogleMock also initialises GoogleTest, and additionally installs + // gmock's verification listener so unmet EXPECT_CALL()s fail the run. + ::testing::InitGoogleMock(&argc, argv); const ::testing::UnitTest &unit_test = *::testing::UnitTest::GetInstance(); std::string filter = UTCore::UT_get_test_filter(); std::vector activeFilters; diff --git a/tests/src/cpp_source/ut_test_gmock.cpp b/tests/src/cpp_source/ut_test_gmock.cpp new file mode 100644 index 0000000..965ea6f --- /dev/null +++ b/tests/src/cpp_source/ut_test_gmock.cpp @@ -0,0 +1,115 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file ut_test_gmock.cpp + * @brief Example: mocking a C++ interface with the ut-core gmock wrappers. + * + * Demonstrates UT_MOCK_METHOD, UT_MOCK_EXPECT_CALL, matchers (UT_MOCK_ANY / UT_MOCK_GE / + * UT_MOCK_STR_EQ), actions (UT_MOCK_RETURN), cardinalities (UT_MOCK_EXACTLY / UT_MOCK_ANY_NUMBER) + * and strictness wrappers (UT_MOCK_NICE) — all without including + * directly. + */ + +#include + +/* ---- Interface under test (normally supplied by a HAL/AIDL header) ------- */ +class ISensor +{ +public: + virtual ~ISensor() = default; + virtual int read(const char *name) = 0; + virtual bool calibrate(int channel, int value) = 0; + virtual void reset() = 0; +}; + +/* ---- Mock of the interface, using the UT_ gmock wrappers ----------------- */ +class MockSensor : public ISensor +{ +public: + UT_MOCK_METHOD(int, read, (const char *name), (override)); + UT_MOCK_METHOD(bool, calibrate, (int channel, int value), (override)); + UT_MOCK_METHOD(void, reset, (), (override)); +}; + +/* ---- A tiny "system under test" that consumes the interface -------------- */ +static bool sensorSelfTest(ISensor &sensor) +{ + sensor.reset(); + if (sensor.read("temperature") < 0) + { + return false; + } + return sensor.calibrate(0, 100); +} + +class UTGMockL1 : public UTCore +{ +public: + UTGMockL1() : UTCore() {} + ~UTGMockL1() override = default; +}; + +/* Register the suite with the ut-core group system, as for any gtest suite. */ +UT_ADD_TEST_TO_GROUP(UTGMockL1, UT_TESTS_L1) + +/* Happy path: exact expectations are all met. */ +UT_ADD_TEST(UTGMockL1, SelfTestPasses) +{ + MockSensor sensor; + UT_MOCK_EXPECT_CALL(sensor, reset()).Times(UT_MOCK_EXACTLY(1)); + UT_MOCK_EXPECT_CALL(sensor, read(UT_MOCK_STR_EQ("temperature"))).WillOnce(UT_MOCK_RETURN(21)); + UT_MOCK_EXPECT_CALL(sensor, calibrate(0, 100)).WillOnce(UT_MOCK_RETURN(true)); + + UT_ASSERT_TRUE(sensorSelfTest(sensor)); +} + +/* Matchers + actions: any channel, any non-negative value calibrates. */ +UT_ADD_TEST(UTGMockL1, MatchersAndActions) +{ + MockSensor sensor; + UT_MOCK_EXPECT_CALL(sensor, reset()).Times(UT_MOCK_ANY_NUMBER); + UT_MOCK_EXPECT_CALL(sensor, read(UT_MOCK_ANY)).WillRepeatedly(UT_MOCK_RETURN(5)); + UT_MOCK_EXPECT_CALL(sensor, calibrate(UT_MOCK_ANY, UT_MOCK_GE(0))).WillOnce(UT_MOCK_RETURN(true)); + + UT_ASSERT_TRUE(sensorSelfTest(sensor)); +} + +/* NiceMock: uninteresting calls (reset/calibrate) are ignored; read drives + * the failure path so we can assert the negative branch. */ +UT_ADD_TEST(UTGMockL1, NiceMockFailurePath) +{ + UT_MOCK_NICE(MockSensor) sensor; + UT_MOCK_EXPECT_CALL(sensor, read(UT_MOCK_ANY)).WillOnce(UT_MOCK_RETURN(-1)); + + UT_ASSERT_FALSE(sensorSelfTest(sensor)); +} + +/* Negative example (kept DISABLED_ so it does not fail the suite): an unmet + * expectation. Remove the DISABLED_ prefix to see gmock fail the run because + * calibrate() is expected but sensorSelfTest short-circuits on read() < 0. */ +UT_ADD_TEST(UTGMockL1, DISABLED_UnmetExpectationFails) +{ + MockSensor sensor; + UT_MOCK_EXPECT_CALL(sensor, reset()).Times(UT_MOCK_ANY_NUMBER); + UT_MOCK_EXPECT_CALL(sensor, read(UT_MOCK_ANY)).WillOnce(UT_MOCK_RETURN(-1)); + UT_MOCK_EXPECT_CALL(sensor, calibrate(UT_MOCK_ANY, UT_MOCK_ANY)).WillOnce(UT_MOCK_RETURN(true)); // never called + + UT_ASSERT_FALSE(sensorSelfTest(sensor)); +}