From: Chris Johnson <chris.n.john...@intel.com> 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 <sean.bro...@microsoft.com> Cc: Michael Kubacki <mikub...@linux.microsoft.com> Cc: Michael D Kinney <michael.d.kin...@intel.com> Signed-off-by: Chris Johnson <chris.n.john...@intel.com> --- .../Include/Library/FunctionMockLib.h | 131 ++++++++++++++++++ .../Include/Library/GoogleTestLib.h | 96 +++++++++++++ .../Library/CmockaLib/CmockaLib.inf | 2 +- .../Library/FunctionMockLib/FunctionMockLib.c | 7 + .../FunctionMockLib/FunctionMockLib.inf | 31 +++++ .../FunctionMockLib/FunctionMockLib.uni | 11 ++ .../Library/GoogleTestLib/GoogleTestLib.inf | 6 +- .../Library/GoogleTestLib/GoogleTestLib.uni | 3 - .../Test/UnitTestFrameworkPkgHostTest.dsc | 1 + .../UnitTestFrameworkPkg.ci.yaml | 7 +- UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec | 2 + .../UnitTestFrameworkPkgHost.dsc.inc | 1 + 12 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h create mode 100644 UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.c create mode 100644 UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf create mode 100644 UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.uni diff --git a/UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h b/UnitTestFrameworkPkg/Include/Library/FunctionMockLib.h new file mode 100644 index 000000000000..bf7a7066560a --- /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 <Library/GoogleTestLib.h> +#include <Library/SubhookLib.h> +#include <type_traits> + +////////////////////////////////////////////////////////////////////////////// +// 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<decltype(&::FUNC), decltype(&MOCK##_##FUNC)>::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 ebec766d4cf7..c0a3d8e66011 100644 --- a/UnitTestFrameworkPkg/Include/Library/GoogleTestLib.h +++ b/UnitTestFrameworkPkg/Include/Library/GoogleTestLib.h @@ -10,5 +10,101 @@ #define GOOGLE_TEST_LIB_H_ #include <gtest/gtest.h> +#include <gmock/gmock.h> +#include <cstring> + +extern "C" { +#include <Uefi.h> +} + +////////////////////////////////////////////////////////////////////////////// +// 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<ArgNum>(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 diff --git a/UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf b/UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf index 052c7f557210..eeee6bc2b3f5 100644 --- a/UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf +++ b/UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf @@ -16,7 +16,7 @@ [Defines] LIBRARY_CLASS = CmockaLib|HOST_APPLICATION # -# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64 +# VALID_ARCHITECTURES = IA32 X64 # [Sources] diff --git a/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.c b/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.c new file mode 100644 index 000000000000..b7bb23f1868b --- /dev/null +++ b/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.c @@ -0,0 +1,7 @@ +/** @file + Macro-only FunctionMockLib library instance with no services. + + Copyright (c) 2023, Intel Corporation. All rights reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ diff --git a/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf b/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf new file mode 100644 index 000000000000..4b0662a8b96d --- /dev/null +++ b/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf @@ -0,0 +1,31 @@ +## @file +# This module provides FunctionMockLib Library implementation. +# +# Copyright (c) 2023, Intel Corporation. All rights reserved.<BR> +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = FunctionMockLib + MODULE_UNI_FILE = FunctionMockLib.uni + FILE_GUID = DF1CAF2F-D584-4EC1-9ABF-07E8B10AD560 + MODULE_TYPE = BASE + VERSION_STRING = 0.1 + LIBRARY_CLASS = FunctionMockLib|HOST_APPLICATION + +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + FunctionMockLib.c + +[LibraryClasses] + GoogleTestLib + SubhookLib + +[Packages] + MdePkg/MdePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec diff --git a/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.uni b/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.uni new file mode 100644 index 000000000000..13e5308ce0e7 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.uni @@ -0,0 +1,11 @@ +// /** @file +// This module provides FunctionMockLib Library implementation. +// +// Copyright (c) 2023, Intel Corporation. All rights reserved.<BR> +// SPDX-License-Identifier: BSD-2-Clause-Patent +// +// **/ + +#string STR_MODULE_ABSTRACT #language en-US "FunctionMockLib Library implementation" + +#string STR_MODULE_DESCRIPTION #language en-US "This module provides FunctionMockLib Library implementation." diff --git a/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf b/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf index 68db75d7023f..1203a0e6eccd 100644 --- a/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf +++ b/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf @@ -16,20 +16,22 @@ [Defines] LIBRARY_CLASS = GoogleTestLib|HOST_APPLICATION # -# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64 +# VALID_ARCHITECTURES = IA32 X64 # [Sources] googletest/googletest/src/gtest-all.cc + googletest/googlemock/src/gmock-all.cc [Packages] + MdePkg/MdePkg.dec UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec [BuildOptions] MSFT:*_*_*_CC_FLAGS == /c /EHsc /Zi MSFT:NOOPT_*_*_CC_FLAGS = /Od - GCC:*_*_*_CC_FLAGS == -g -c + GCC:*_*_*_CC_FLAGS == -g -c -fshort-wchar GCC:NOOPT_*_*_CC_FLAGS = -O0 GCC:*_*_IA32_CC_FLAGS = -m32 diff --git a/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.uni b/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.uni index 14c862a23744..695588ce3f46 100644 --- a/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.uni +++ b/UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.uni @@ -1,10 +1,7 @@ // /** @file // This module provides GoogleTest Library implementation. // -// This module provides GoogleTest Library implementation. -// // Copyright (c) 2022, Intel Corporation. All rights reserved.<BR> -// // SPDX-License-Identifier: BSD-2-Clause-Patent // // **/ diff --git a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc index 722509c8f26f..f14961ad86d0 100644 --- a/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc +++ b/UnitTestFrameworkPkg/Test/UnitTestFrameworkPkgHostTest.dsc @@ -34,6 +34,7 @@ [Components] UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf UnitTestFrameworkPkg/Library/SubhookLib/SubhookLib.inf + UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf UnitTestFrameworkPkg/Library/Posix/DebugLibPosix/DebugLibPosix.inf UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLibCmocka.inf diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml index d8f8e024c476..be839d1af0a7 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.ci.yaml @@ -79,9 +79,14 @@ "AuditOnly": False, # Fails test but run in AuditOnly mode to collect log "IgnoreFiles": [ # use gitignore syntax to ignore errors in matching files "Library/CmockaLib/cmocka/**/*.*", # not going to spell check a submodule - "Library/GoogleTestLib/googletest/**/*.*" # not going to spell check a submodule + "Library/GoogleTestLib/googletest/**/*.*", # not going to spell check a submodule + "Library/SubhookLib/subhook/**/*.*" # not going to spell check a submodule ], "ExtendWords": [ # words to extend to the dictionary for this package + "Pointee", + "gmock", + "GMOCK", + "DSUBHOOK", "testcase", "testsuites", "cmocka", diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec index 30b489915d4a..ef0a148d4876 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec @@ -26,6 +26,7 @@ [Includes.Common.Private] PrivateInclude Library/CmockaLib/cmocka/include/cmockery Library/GoogleTestLib/googletest/googletest + Library/GoogleTestLib/googletest/googlemock [LibraryClasses] ## @libraryclass Allows save and restore unit test internal state @@ -36,6 +37,7 @@ [LibraryClasses] # GoogleTestLib|Include/Library/GoogleTestLib.h SubhookLib|Include/Library/SubhookLib.h + FunctionMockLib|Include/Library/FunctionMockLib.h [LibraryClasses.Common.Private] ## @libraryclass Provides a unit test result report diff --git a/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc b/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc index e77897bd326f..7866c36e6693 100644 --- a/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc +++ b/UnitTestFrameworkPkg/UnitTestFrameworkPkgHost.dsc.inc @@ -16,6 +16,7 @@ [LibraryClasses.common.HOST_APPLICATION] CmockaLib|UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf GoogleTestLib|UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf SubhookLib|UnitTestFrameworkPkg/Library/SubhookLib/SubhookLib.inf + FunctionMockLib|UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf UnitTestLib|UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLibCmocka.inf DebugLib|UnitTestFrameworkPkg/Library/Posix/DebugLibPosix/DebugLibPosix.inf MemoryAllocationLib|UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf -- 2.39.1.windows.1 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#102521): https://edk2.groups.io/g/devel/message/102521 Mute This Topic: https://groups.io/mt/98066295/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/leave/9847357/21656/1706620634/xyzzy [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-