🤪
グローバル関数を選択的に透過可能な状態でモックする ~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