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