1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#ifndef IAM_UNITTEST_C_MOCKER_H
17#define IAM_UNITTEST_C_MOCKER_H
18
19#include <dlfcn.h>
20#include <mutex>
21
22#include <gmock/gmock.h>
23
24namespace OHOS {
25namespace UserIam {
26namespace UserAuth {
27
28template <typename T>
29class CMocker {
30public:
31    CMocker()
32    {
33        std::lock_guard<std::mutex> lock(mutex_);
34        EXPECT_EQ(instance_, nullptr);
35        instance_ = static_cast<T *>(this);
36    }
37
38    virtual ~CMocker()
39    {
40        std::lock_guard<std::mutex> lock(mutex_);
41        EXPECT_EQ(instance_, this);
42        instance_ = nullptr;
43    }
44    static inline T *GetInstance()
45    {
46        return instance_;
47    }
48
49    static inline std::mutex &GetMutex()
50    {
51        return mutex_;
52    }
53
54private:
55    static T *instance_;
56    static std::mutex mutex_;
57};
58
59template <typename T>
60T *CMocker<T>::instance_ = nullptr;
61
62template <typename T>
63std::mutex CMocker<T>::mutex_ {};
64
65#define SIGNATURE(ret, args) (GMOCK_INTERNAL_SIGNATURE(ret, args))
66
67#define PARAMETER(index, signature, dummy) \
68    GMOCK_PP_COMMA_IF(index) GMOCK_INTERNAL_ARG_O(index, GMOCK_PP_REMOVE_PARENS(signature))
69
70#define DECLARE_METHOD(ret, method, args)                                                                  \
71public:                                                                                                    \
72    MOCK_METHOD(ret, method, args);                                                                        \
73    using typeof##method = ret (*)(GMOCK_PP_REPEAT(PARAMETER, SIGNATURE(ret, args), GMOCK_PP_NARG0 args)); \
74    using get##method = std::function<typeof##method()>;                                                   \
75    const static typeof##method default##method;
76
77#define IMPLEMENT_FUNCTION_INTERNAL(cls, method, count, signature, invoker)                               \
78    const cls::typeof##method cls::default##method = reinterpret_cast<cls::typeof##method>(invoker);      \
79    testing::internal::Function<GMOCK_PP_REMOVE_PARENS(signature)>::Result method(                        \
80        GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, signature, count))                                      \
81    {                                                                                                     \
82        const std::lock_guard<std::mutex> lock(cls::GetMutex());                                          \
83        static auto lookup = reinterpret_cast<cls::typeof##method>(dlsym(RTLD_NEXT, #method));            \
84                                                                                                          \
85        auto *mock = cls::GetInstance();                                                                  \
86        auto *stub = cls::default##method != nullptr ? cls::default##method : lookup;                     \
87                                                                                                          \
88        if (mock != nullptr && stub != nullptr) {                                                         \
89            ON_CALL(*mock, method).WillByDefault(stub);                                                   \
90        }                                                                                                 \
91                                                                                                          \
92        if (mock != nullptr) {                                                                            \
93            return mock->method(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, signature, count));           \
94        }                                                                                                 \
95                                                                                                          \
96        if (stub != nullptr) {                                                                            \
97            return stub(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, signature, count));                   \
98        }                                                                                                 \
99                                                                                                          \
100        testing::internal::Log(testing::internal::kWarning, #method " invoked without an implement.", 0); \
101        return testing::internal::Function<GMOCK_PP_REMOVE_PARENS(signature)>::Result();                  \
102    }
103
104#define IMPLEMENT_FUNCTION_WITH_INVOKER(cls, ret, method, args, invoker) \
105    IMPLEMENT_FUNCTION_INTERNAL(cls, method, GMOCK_PP_NARG0 args, SIGNATURE(ret, args), invoker)
106
107#define IMPLEMENT_FUNCTION(cls, ret, method, args) \
108    IMPLEMENT_FUNCTION_WITH_INVOKER(cls, ret, method, args, static_cast<void *>(nullptr))
109
110} // namespace UserAuth
111} // namespace UserIam
112} // namespace OHOS
113
114#endif // IAM_UNITTEST_C_MOCKER_H