From d0252b8fc1fe8b6489a4d0a25c29a5d3eaa95228 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Fri, 24 Mar 2023 16:43:10 -0700 Subject: UnitTestFrameworkPkg: Add gmock support to GoogleTestLib REF: https://bugzilla.tianocore.org/show_bug.cgi?id=4389 * Add gmock support to GoogleTestLib * Add FunctionMockLib library class and library instance * Add GoogleTest extension to GoogleTestLib.h for CHAR16 type * Add GoogleTest extension to GoogleTestLib.h for buffer types * HOST_APPLICATION only supports IA32/X64 Cc: Sean Brogan Cc: Michael Kubacki Cc: Michael D Kinney Signed-off-by: Chris Johnson Reviewed-by: Michael Kubacki Reviewed-by: Oliver Smith-Denny Reviewed-by: Michael D Kinney --- .../Include/Library/FunctionMockLib.h | 131 +++++++++++++++++++++ .../Include/Library/GoogleTestLib.h | 96 +++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h (limited to 'UnitTestFrameworkPkg/Include') diff --git a/UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h b/UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h new file mode 100644 index 0000000000..bf7a706656 --- /dev/null +++ b/UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h @@ -0,0 +1,131 @@ +/** @file + This header allows the mocking of free (C style) functions using gmock. + + Copyright (c) 2023, Intel Corporation. All rights reserved. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef FUNCTION_MOCK_LIB_H_ +#define FUNCTION_MOCK_LIB_H_ + +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// +// The below macros are the public function mock interface that are intended +// to be used outside this file. + +#define MOCK_INTERFACE_DECLARATION(MOCK) \ + static MOCK * Instance; \ + MOCK (); \ + ~MOCK (); + +#define MOCK_INTERFACE_DEFINITION(MOCK) \ + MOCK_STATIC_INSTANCE_DEFINITION (MOCK) \ + MOCK_INTERFACE_CONSTRUCTOR (MOCK) \ + MOCK_INTERFACE_DECONSTRUCTOR (MOCK) + +// Mock function declaration for external functions (i.e. functions to +// mock that do not exist in the compilation unit). +#define MOCK_FUNCTION_DECLARATION(RET_TYPE, FUNC, ARGS) \ + MOCK_FUNCTION_TYPE_DEFINITIONS(RET_TYPE, FUNC, ARGS) \ + MOCK_METHOD (RET_TYPE, FUNC, ARGS); + +// Mock function definition for external functions (i.e. functions to +// mock that do not exist in the compilation unit). +#define MOCK_FUNCTION_DEFINITION(MOCK, FUNC, NUM_ARGS, CALL_TYPE) \ + FUNCTION_DEFINITION_TO_CALL_MOCK(MOCK, FUNC, FUNC, NUM_ARGS, CALL_TYPE) + +// Mock function declaration for internal functions (i.e. functions to +// mock that already exist in the compilation unit). +#define MOCK_FUNCTION_INTERNAL_DECLARATION(RET_TYPE, FUNC, ARGS) \ + MOCK_FUNCTION_DECLARATION(RET_TYPE, FUNC, ARGS) \ + MOCK_FUNCTION_HOOK_DECLARATIONS(FUNC) + +// Mock function definition for internal functions (i.e. functions to +// mock that already exist in the compilation unit). This definition also +// implements MOCK_FUNC() which is later hooked as FUNC(). +#define MOCK_FUNCTION_INTERNAL_DEFINITION(MOCK, FUNC, NUM_ARGS, CALL_TYPE) \ + FUNCTION_DEFINITION_TO_CALL_MOCK(MOCK, FUNC, MOCK##_##FUNC, NUM_ARGS, CALL_TYPE) \ + MOCK_FUNCTION_HOOK_DEFINITIONS(MOCK, FUNC) + +////////////////////////////////////////////////////////////////////////////// +// The below macros are private and should not be used outside this file. + +#define MOCK_FUNCTION_HOOK_DECLARATIONS(FUNC) \ + static subhook::Hook Hook##FUNC; \ + struct MockContainer_##FUNC { \ + MockContainer_##FUNC (); \ + ~MockContainer_##FUNC (); \ + }; \ + MockContainer_##FUNC MockContainerInst_##FUNC; + +// This definition implements a constructor and destructor inside a nested +// class to enable automatic installation of the hooks to the associated +// MOCK_FUNC() when the mock object is instantiated in scope and automatic +// removal when the instantiated mock object goes out of scope. +#define MOCK_FUNCTION_HOOK_DEFINITIONS(MOCK, FUNC) \ + subhook :: Hook MOCK :: Hook##FUNC; \ + MOCK :: MockContainer_##FUNC :: MockContainer_##FUNC () { \ + if (MOCK :: Instance == NULL) \ + MOCK :: Hook##FUNC .Install( \ + (FUNC##_ret_type *) ::FUNC, \ + (FUNC##_ret_type *) MOCK##_##FUNC); \ + } \ + MOCK :: MockContainer_##FUNC :: ~MockContainer_##FUNC () { \ + MOCK :: Hook##FUNC .Remove(); \ + } \ + static_assert( \ + std::is_same::value, \ + "Function signature from 'MOCK_FUNCTION_INTERNAL_DEFINITION' macro " \ + "invocation for '" #FUNC "' does not match the function signature " \ + "of '" #FUNC "' function it is mocking. Mismatch could be due to " \ + "different return type, arguments, or calling convention. See " \ + "associated 'MOCK_FUNCTION_INTERNAL_DECLARATION' macro invocation " \ + "for more details."); + +#define MOCK_FUNCTION_TYPE_DEFINITIONS(RET_TYPE, FUNC, ARGS) \ + using FUNC##_ret_type = RET_TYPE; \ + using FUNC##_type = FUNC##_ret_type ARGS; + +// This function definition simply calls MOCK::Instance->FUNC() which is the +// mocked counterpart of the original function. This allows using gmock with +// C free functions (since by default gmock only works with object methods). +#define FUNCTION_DEFINITION_TO_CALL_MOCK(MOCK, FUNC, FUNC_DEF_NAME, NUM_ARGS, CALL_TYPE) \ + extern "C" { \ + typename MOCK :: FUNC##_ret_type CALL_TYPE FUNC_DEF_NAME( \ + GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, \ + (MOCK :: FUNC##_type), \ + NUM_ARGS)) \ + { \ + EXPECT_TRUE(MOCK :: Instance != NULL) \ + << "Called '" #FUNC "' in '" #MOCK "' function mock object before " \ + << "an instance of '" #MOCK "' was created in test '" \ + << ::testing::UnitTest::GetInstance()->current_test_info()->name() \ + << "'."; \ + return MOCK :: Instance->FUNC( \ + GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, \ + (MOCK :: FUNC##_type), \ + NUM_ARGS)); \ + } \ + } + +#define MOCK_STATIC_INSTANCE_DEFINITION(MOCK) MOCK * MOCK :: Instance = NULL; + +#define MOCK_INTERFACE_CONSTRUCTOR(MOCK) \ + MOCK :: MOCK () { \ + EXPECT_TRUE(MOCK :: Instance == NULL) \ + << "Multiple instances of '" #MOCK "' function mock object were " \ + << "created and only one instance is allowed in test '" \ + << ::testing::UnitTest::GetInstance()->current_test_info()->name() \ + << "'."; \ + MOCK :: Instance = this; \ + } + +#define MOCK_INTERFACE_DECONSTRUCTOR(MOCK) \ + MOCK :: ~ MOCK () { \ + MOCK :: Instance = NULL; \ + } + +#endif // FUNCTION_MOCK_LIB_H_ diff --git a/UnitTestFrameworkPkg/Include/Library/GoogleTestLib.h b/UnitTestFrameworkPkg/Include/Library/GoogleTestLib.h index ebec766d4c..c0a3d8e660 100644 --- a/UnitTestFrameworkPkg/Include/Library/GoogleTestLib.h +++ b/UnitTestFrameworkPkg/Include/Library/GoogleTestLib.h @@ -10,5 +10,101 @@ #define GOOGLE_TEST_LIB_H_ #include +#include +#include + +extern "C" { +#include +} + +////////////////////////////////////////////////////////////////////////////// +// Below are the action extensions to GoogleTest and gmock for EDK2 types. +// These actions are intended to be used in EXPECT_CALL (and related gmock +// macros) to support assignments to output arguments in the expected call. +// + +// Action to support pointer types to a buffer (such as UINT8* or VOID*) +ACTION_TEMPLATE ( + SetArgBuffer, + HAS_1_TEMPLATE_PARAMS (size_t, ArgNum), + AND_2_VALUE_PARAMS (Buffer, ByteSize) + ) { + auto ArgBuffer = std::get(args); + + std::memcpy (ArgBuffer, Buffer, ByteSize); +} + +////////////////////////////////////////////////////////////////////////////// +// Below are the matcher extensions to GoogleTest and gmock for EDK2 types. +// These matchers are intended to be used in EXPECT_CALL (and related gmock +// macros) to support comparisons to input arguments in the expected call. +// +// Note that these matchers can also be used in the EXPECT_THAT or ASSERT_THAT +// macros to compare whether two values are equal. +// + +// Matcher to support pointer types to a buffer (such as UINT8* or VOID* or +// any structure pointer) +MATCHER_P2 ( + BufferEq, + Buffer, + ByteSize, + std::string ("buffer data to ") + (negation ? "not " : "") + "be the same" + ) { + UINT8 *Actual = (UINT8 *)arg; + UINT8 *Expected = (UINT8 *)Buffer; + + for (size_t i = 0; i < ByteSize; i++) { + if (Actual[i] != Expected[i]) { + *result_listener << "byte at offset " << i + << " does not match expected. [" << std::hex + << "Actual: 0x" << std::setw (2) << std::setfill ('0') + << (unsigned int)Actual[i] << ", " + << "Expected: 0x" << std::setw (2) << std::setfill ('0') + << (unsigned int)Expected[i] << "]"; + return false; + } + } + + *result_listener << "all bytes match"; + return true; +} + +// Matcher to support CHAR16* type +MATCHER_P ( + Char16StrEq, + String, + std::string ("strings to ") + (negation ? "not " : "") + "be the same" + ) { + CHAR16 *Actual = (CHAR16 *)arg; + CHAR16 *Expected = (CHAR16 *)String; + + for (size_t i = 0; Actual[i] != 0; i++) { + if (Actual[i] != Expected[i]) { + *result_listener << "character at offset " << i + << " does not match expected. [" << std::hex + << "Actual: 0x" << std::setw (4) << std::setfill ('0') + << Actual[i]; + + if (std::isprint (Actual[i])) { + *result_listener << " ('" << (char)Actual[i] << "')"; + } + + *result_listener << ", Expected: 0x" << std::setw (4) << std::setfill ('0') + << Expected[i]; + + if (std::isprint (Expected[i])) { + *result_listener << " ('" << (char)Expected[i] << "')"; + } + + *result_listener << "]"; + + return false; + } + } + + *result_listener << "strings match"; + return true; +} #endif -- cgit v1.2.3