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 <sean.brogan@microsoft.com>
Cc: Michael Kubacki <mikuback@linux.microsoft.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Signed-off-by: Chris Johnson <chris.n.johnson@intel.com>
Reviewed-by: Michael Kubacki <michael.kubacki@microsoft.com>
Reviewed-by: Oliver Smith-Denny <osde@linux.microsoft.com>
Reviewed-by: Michael D Kinney <michael.d.kinney@intel.com>
This commit is contained in:
Chris Johnson 2023-03-24 16:43:10 -07:00 committed by mergify[bot]
parent caa389625f
commit d0252b8fc1
12 changed files with 297 additions and 18 deletions

View File

@ -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_

View File

@ -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

View File

@ -16,7 +16,7 @@
LIBRARY_CLASS = CmockaLib|HOST_APPLICATION
#
# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64
# VALID_ARCHITECTURES = IA32 X64
#
[Sources]

View File

@ -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
**/

View File

@ -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 = 0x00010018
BASE_NAME = FunctionMockLib
MODULE_UNI_FILE = FunctionMockLib.uni
FILE_GUID = DF1CAF2F-D584-4EC1-9ABF-07E8B10AD560
MODULE_TYPE = HOST_APPLICATION
VERSION_STRING = 0.1
LIBRARY_CLASS = FunctionMockLib
#
# VALID_ARCHITECTURES = IA32 X64
#
[Sources]
FunctionMockLib.c
[LibraryClasses]
GoogleTestLib
SubhookLib
[Packages]
MdePkg/MdePkg.dec
UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec

View File

@ -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."

View File

@ -7,30 +7,27 @@
##
[Defines]
INF_VERSION = 0x00010005
INF_VERSION = 0x00010018
BASE_NAME = GoogleTestLib
MODULE_UNI_FILE = GoogleTestLib.uni
FILE_GUID = A90E4751-AD30-43CC-980B-01E356B49ADF
MODULE_TYPE = BASE
MODULE_TYPE = HOST_APPLICATION
VERSION_STRING = 0.1
LIBRARY_CLASS = GoogleTestLib|HOST_APPLICATION
LIBRARY_CLASS = GoogleTestLib
#
# 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:NOOPT_*_*_CC_FLAGS = -O0
GCC:*_*_IA32_CC_FLAGS = -m32
GCC:*_*_X64_CC_FLAGS = -m64
MSFT:*_*_*_CC_FLAGS == /c /EHsc /Zi /Od
GCC:*_*_IA32_CC_FLAGS == -g -c -fshort-wchar -O0 -m32
GCC:*_*_X64_CC_FLAGS == -g -c -fshort-wchar -O0 -m64

View File

@ -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
//
// **/

View File

@ -32,8 +32,9 @@
# Build HOST_APPLICATION Libraries
#
UnitTestFrameworkPkg/Library/CmockaLib/CmockaLib.inf
UnitTestFrameworkPkg/Library/FunctionMockLib/FunctionMockLib.inf
UnitTestFrameworkPkg/Library/GoogleTestLib/GoogleTestLib.inf
UnitTestFrameworkPkg/Library/SubhookLib/SubhookLib.inf
UnitTestFrameworkPkg/Library/Posix/DebugLibPosix/DebugLibPosix.inf
UnitTestFrameworkPkg/Library/Posix/MemoryAllocationLibPosix/MemoryAllocationLibPosix.inf
UnitTestFrameworkPkg/Library/SubhookLib/SubhookLib.inf
UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLibCmocka.inf

View File

@ -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",

View File

@ -26,6 +26,7 @@
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 @@
#
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

View File

@ -16,6 +16,7 @@
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