🤪

グローバル関数を選択的に透過可能な状態でモックする ~pthreadの例~

に公開

概要

ローカルで同名かつ同じ型の関数を定義することで、リンク時に我々が定義した関数実装が利用される。しかし、この方法だとモックではないpthreadAPIを利用したくなったときにできない。そこで、RTLD_NEXTを使うことで次に定義された実装を呼び出すことができる。

このテクニックは幅広いグローバル関数をモックするうえで役に立ちそう。

コード

namespace {
class MockPthread {
   public:
    struct Instance {
        MOCK_METHOD(int, pthread_create,
                    (pthread_t *__restrict __newthread,
                     const pthread_attr_t *__restrict __attr,
                     void *(*__start_routine)(void *), void *__restrict __arg),
                    (__THROWNL));
    };

    static inline std::unique_ptr<::testing::StrictMock<Instance>> _instance{};
    static inline auto &instance() { return *_instance; }
    static inline auto instance_available() { return bool(_instance); }
    static inline auto init() {
        _instance = std::make_unique<::testing::StrictMock<Instance>>();
    }
    static inline auto deinit() { _instance.reset(); }

   public:
    static inline int pthread_create(pthread_t *__restrict __newthread,
                                     const pthread_attr_t *__restrict __attr,
                                     void *(*__start_routine)(void *),
                                     void *__restrict __arg) __THROWNL {
        return MockPthread::instance().pthread_create(__newthread, __attr,
                                                      __start_routine, __arg);
    }
};

template <typename Base>
class MockPthreadMixIn : public Base {
   protected:
    void SetUp() override {
        Base::SetUp();

        MockPthread::init();
    }

    void TearDown() override {
        MockPthread::deinit();

        Base::TearDown();
    }
};

}  // namespace

extern "C" {
int pthread_create(pthread_t *__restrict __newthread,
                   const pthread_attr_t *__restrict __attr,
                   void *(*__start_routine)(void *), void *__restrict __arg) {
    if (MockPthread::instance_available()) {
        return MockPthread::pthread_create(__newthread, __attr, __start_routine,
                                           __arg);
    }

    static typeof(pthread_create) *real_pthread_create = NULL;

    if (!real_pthread_create) {
        real_pthread_create =
            (typeof(real_pthread_create))dlsym(RTLD_NEXT, "pthread_create");
    }

    return real_pthread_create(__newthread, __attr, __start_routine, __arg);
}
}

Discussion