요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
========================
Function Redirection API
========================
Overview
========
When writing unit tests, it's important to be able to isolate the code being
tested from other parts of the kernel. This ensures the reliability of the test
(it won't be affected by external factors), reduces dependencies on specific
hardware or config options (making the test easier to run), and protects the
stability of the rest of the system (making it less likely for test-specific
state to interfere with the rest of the system).
While for some code (typically generic data structures, helpers, and other
"pure functions") this is trivial, for others (like device drivers,
filesystems, core subsystems) the code is heavily coupled with other parts of
the kernel.
This coupling is often due to global state in some way: be it a global list of
devices, the filesystem, or some hardware state. Tests need to either carefully
manage, isolate, and restore state, or they can avoid it altogether by
replacing access to and mutation of this state with a "fake" or "mock" variant.
By refactoring access to such state, such as by introducing a layer of
indirection which can use or emulate a separate set of test state. However,
such refactoring comes with its own costs (and undertaking significant
refactoring before being able to write tests is suboptimal).
A simpler way to intercept and replace some of the function calls is to use
function redirection via static stubs.
Static Stubs
============
Static stubs are a way of redirecting calls to one function (the "real"
function) to another function (the "replacement" function).
It works by adding a macro to the "real" function which checks to see if a test
is running, and if a replacement function is available. If so, that function is
called in place of the original.
Using static stubs is pretty straightforward:
1. Add the KUNIT_STATIC_STUB_REDIRECT() macro to the start of the "real"
function.
This should be the first statement in the function, after any variable
declarations. KUNIT_STATIC_STUB_REDIRECT() takes the name of the
function, followed by all of the arguments passed to the real function.
For example:
.. code-block:: c
void send_data_to_hardware(const char *str)
{
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
/* real implementation */
}
2. Write one or more replacement functions.
These functions should have the same function signature as the real function.
In the event they need to access or modify test-specific state, they can use
kunit_get_current_test() to get a struct kunit pointer. This can then
be passed to the expectation/assertion macros, or used to look up KUnit
resources.
For example:
.. code-block:: c
void fake_send_data_to_hardware(const char *str)
{
struct kunit *test = kunit_get_current_test();
KUNIT_EXPECT_STREQ(test, str, "Hello World!");
}
3. Activate the static stub from your test.
From within a test, the redirection can be enabled with
kunit_activate_static_stub(), which accepts a struct kunit pointer,
the real function, and the replacement function. You can call this several
times with different replacement functions to swap out implementations of the
function.
In our example, this would be
.. code-block:: c
kunit_activate_static_stub(test,
send_data_to_hardware,
fake_send_data_to_hardware);
4. Call (perhaps indirectly) the real function.
Once the redirection is activated, any call to the real function will call
the replacement function instead. Such calls may be buried deep in the
implementation of another function, but must occur from the test's kthread.
For example:
.. code-block:: c
send_data_to_hardware("Hello World!"); /* Succeeds */
send_data_to_hardware("Something else"); /* Fails the test. */
5. (Optionally) disable the stub.
When you no longer need it, disable the redirection (and hence resume the
original behaviour of the 'real' function) using
kunit_deactivate_static_stub(). Otherwise, it will be automatically disabled
when the test exits.
For example:
.. code-block:: c
kunit_deactivate_static_stub(test, send_data_to_hardware);
It's also possible to use these replacement functions to test to see if a
function is called at all, for example:
.. code-block:: c
void send_data_to_hardware(const char *str)
{
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
/* real implementation */
}
/* In test file */
int times_called = 0;
void fake_send_data_to_hardware(const char *str)
{
times_called++;
}
...
/* In the test case, redirect calls for the duration of the test */
kunit_activate_static_stub(test, send_data_to_hardware, fake_send_data_to_hardware);
send_data_to_hardware("hello");
KUNIT_EXPECT_EQ(test, times_called, 1);
/* Can also deactivate the stub early, if wanted */
kunit_deactivate_static_stub(test, send_data_to_hardware);
send_data_to_hardware("hello again");
KUNIT_EXPECT_EQ(test, times_called, 1);
API Reference
=============
.. kernel-doc:: include/kunit/static_stub.h
:internal:
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
함수 redirection이 필요한 이유
1-35SPDX 라이선스 식별자: GPL-2.0
Function Redirection API
개요
Unit test를 작성할 때는 검사 대상 코드를 커널의 다른 부분과 격리할 수 있어야 합니다. 그래야 외부 요인에 영향을 받지 않아 테스트의 신뢰성이 높아지고, 특정 hardware나 config option에 대한 dependency가 줄어 테스트를 쉽게 실행할 수 있으며, 테스트 전용 상태가 시스템의 나머지 부분에 간섭할 가능성이 낮아져 전체 시스템의 안정성을 보호할 수 있습니다.
일반적인 data structure, helper, 기타 pure function처럼 쉽게 격리되는 코드도 있지만 device driver, filesystem, core subsystem처럼 커널의 다른 부분과 강하게 결합된 코드도 있습니다.
이 결합은 흔히 어떤 형태로든 global state에서 비롯됩니다. 전체 device 목록, filesystem, hardware state가 그 예입니다. 테스트는 상태를 신중히 관리하고 격리한 뒤 복원하거나, state 접근과 변경을 fake 또는 mock variant로 바꾸어 실제 state를 전혀 건드리지 않을 수 있습니다.
별도의 test state 집합을 사용하거나 모방하는 indirection layer를 도입하는 식으로 state 접근을 refactoring할 수 있습니다. 하지만 refactoring 자체에도 비용이 들며, 테스트를 작성하기 전에 큰 refactoring부터 수행해야 하는 것은 바람직하지 않습니다.
일부 함수 호출을 더 간단히 가로채고 교체하는 방법은 static stub을 통한 function redirection을 사용하는 것입니다.
Static stub 설정과 해제
36-125Static Stub
Static stub은 한 함수, 즉 real function으로 향하는 호출을 다른 replacement function으로 redirect하는 방법입니다.
Real function에 macro를 추가하면 현재 테스트가 실행 중인지와 replacement function이 제공되었는지를 검사합니다. 둘 다 만족하면 원래 함수 대신 replacement function을 호출합니다.
Static stub 사용 절차는 다음과 같습니다.
1. Real function 시작 부분에 KUNIT_STATIC_STUB_REDIRECT() macro를 추가합니다. Variable 선언 다음의 첫 statement여야 합니다. Macro 인수에는 function 이름과 real function에 전달된 모든 argument를 순서대로 넣습니다.
void send_data_to_hardware(const char *str)
{
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
/* real implementation */
}
2. Replacement function을 하나 이상 작성합니다. 이 함수는 real function과 같은 function signature를 가져야 합니다. Test 전용 state에 접근하거나 이를 수정해야 한다면 kunit_get_current_test()로 struct kunit pointer를 가져올 수 있습니다. 이 pointer를 expectation 또는 assertion macro에 전달하거나 KUnit resource 조회에 사용할 수 있습니다.
void fake_send_data_to_hardware(const char *str)
{
struct kunit *test = kunit_get_current_test();
KUNIT_EXPECT_STREQ(test, str, "Hello World!");
}
3. 테스트에서 static stub을 활성화합니다. kunit_activate_static_stub()은 struct kunit pointer, real function, replacement function을 받습니다. 서로 다른 replacement function으로 여러 번 호출하여 구현을 바꿀 수 있습니다.
kunit_activate_static_stub(test,
send_data_to_hardware,
fake_send_data_to_hardware);
4. Real function을 직접 또는 간접적으로 호출합니다. Redirection이 활성화된 뒤에는 real function 호출이 replacement function을 호출합니다. 이런 호출은 다른 함수 구현의 깊은 곳에 있을 수 있지만 반드시 테스트의 kthread에서 발생해야 합니다.
send_data_to_hardware("Hello World!"); /* Succeeds */
send_data_to_hardware("Something else"); /* Fails the test. */
5. 필요하다면 stub을 비활성화합니다. 더 이상 필요하지 않으면 kunit_deactivate_static_stub()으로 redirection을 끄고 real function의 원래 동작으로 돌아갑니다. 명시적으로 끄지 않아도 테스트 종료 시 자동으로 비활성화됩니다.
kunit_deactivate_static_stub(test, send_data_to_hardware);
테스트가 real function 호출을 replacement function으로 전환하고 종료 시 복원하는 과정입니다.
호출 여부와 횟수 검증
126-157Replacement function을 사용해 함수가 실제로 호출되는지 검사할 수도 있습니다. 다음 예에서는 counter를 증가시키는 fake function으로 redirect하고, 해제 전후의 호출 횟수를 확인합니다.
void send_data_to_hardware(const char *str)
{
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
/* real implementation */
}
/* In test file */
int times_called = 0;
void fake_send_data_to_hardware(const char *str)
{
times_called++;
}
...
/* In the test case, redirect calls for the duration of the test */
kunit_activate_static_stub(test, send_data_to_hardware, fake_send_data_to_hardware);
send_data_to_hardware("hello");
KUNIT_EXPECT_EQ(test, times_called, 1);
/* Can also deactivate the stub early, if wanted */
kunit_deactivate_static_stub(test, send_data_to_hardware);
send_data_to_hardware("hello again");
KUNIT_EXPECT_EQ(test, times_called, 1);
Static stub 활성화와 해제에 따라 real call이 counter에 미치는 영향을 정리했습니다.
API reference
158-162API Reference
다음 kernel-doc 지시문은 `include/kunit/static_stub.h`의 internal API 문서를 포함합니다.
.. kernel-doc:: include/kunit/static_stub.h
:internal:
요약과 해설
functionredirection.rst:1-162Static stub은 큰 refactoring 없이 hardware나 global state에 강하게 결합된 호출을 test replacement로 바꿉니다. Real function 첫 statement에 redirect macro를 두고 동일 signature의 fake를 등록하면 테스트 kthread에서 발생하는 호출만 교체할 수 있습니다.
Replacement는 kunit_get_current_test()로 현재 test state와 assertion API를 사용할 수 있습니다. Stub은 다른 implementation으로 교체하거나 일찍 해제할 수 있으며 해제하지 않아도 test 종료 시 자동 정리됩니다.