函数重定向 API

概述

在编写单元测试时,能够将正在测试的代码与内核的其他部分隔离非常重要。这可以确保测试的可靠性(它不会受到外部因素的影响),减少对特定硬件或配置选项的依赖(使测试更容易运行),并保护系统其余部分的稳定性(降低测试特定状态干扰系统其余部分的可能性)。

虽然对于某些代码(通常是通用数据结构、帮助程序和其他“纯函数”)来说,这很简单,但对于其他代码(如设备驱动程序、文件系统、核心子系统)来说,代码与内核的其他部分高度耦合。

这种耦合通常是由于某种形式的全局状态:例如,全局设备列表、文件系统或某些硬件状态。测试需要仔细管理、隔离和恢复状态,或者可以通过用“伪”或“模拟”变体替换对此状态的访问和修改来完全避免它。

通过重构对此类状态的访问,例如引入一个可以使用或模拟单独的测试状态的间接层。但是,这种重构有其自身的成本(并且在能够编写测试之前进行重大重构是次优的)。

一种拦截和替换某些函数调用的更简单方法是使用通过静态存根进行的函数重定向。

静态存根

静态存根是一种将对一个函数(“真实”函数)的调用重定向到另一个函数(“替换”函数)的方法。

它的工作原理是在“真实”函数中添加一个宏,该宏检查是否正在运行测试,以及是否有可用的替换函数。如果有,则调用该函数来代替原始函数。

使用静态存根非常简单

  1. KUNIT_STATIC_STUB_REDIRECT() 宏添加到“真实”函数的开头。

    这应该是函数中的第一个语句,在任何变量声明之后。 KUNIT_STATIC_STUB_REDIRECT() 接收函数名称,后跟传递给真实函数的所有参数。

    例如

    void send_data_to_hardware(const char *str)
    {
            KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
            /* real implementation */
    }
    
  2. 编写一个或多个替换函数。

    这些函数应具有与真实函数相同的函数签名。如果它们需要访问或修改特定于测试的状态,它们可以使用 kunit_get_current_test() 来获取一个 struct kunit 指针。然后,可以将此指针传递给期望/断言宏,或者用于查找 KUnit 资源。

    例如

    void fake_send_data_to_hardware(const char *str)
    {
            struct kunit *test = kunit_get_current_test();
            KUNIT_EXPECT_STREQ(test, str, "Hello World!");
    }
    
  3. 从您的测试中激活静态存根。

    在测试中,可以使用 kunit_activate_static_stub() 启用重定向,它接受一个 struct kunit 指针、真实函数和替换函数。您可以多次调用此函数,并使用不同的替换函数来交换函数的实现。

    在我们的示例中,这将是

    kunit_activate_static_stub(test,
                               send_data_to_hardware,
                               fake_send_data_to_hardware);
    
  4. 调用(可能间接地)真实函数。

    一旦激活重定向,对真实函数的任何调用都将改为调用替换函数。此类调用可能深埋在另一个函数的实现中,但必须从测试的 kthread 中发生。

    例如

    send_data_to_hardware("Hello World!"); /* Succeeds */
    send_data_to_hardware("Something else"); /* Fails the test. */
    
  5. (可选)禁用存根。

    当您不再需要它时,可以使用 kunit_deactivate_static_stub() 禁用重定向(因此恢复“真实”函数的原始行为)。否则,它将在测试退出时自动禁用。

    例如

    kunit_deactivate_static_stub(test, send_data_to_hardware);
    

也可以使用这些替换函数来测试是否根本调用了某个函数,例如

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 参考

KUNIT_STATIC_STUB_REDIRECT

KUNIT_STATIC_STUB_REDIRECT (real_fn_name, args...)

如果存在替换的“静态存根”,则调用它

参数

real_fn_name

此函数的名称(作为标识符,而不是字符串)

args...

传递给此函数的所有参数

描述

这是一个函数序言,用于允许 KUnit 测试重定向对当前函数的调用。 KUnit 测试可以调用 kunit_activate_static_stub() 来传递一个替换函数。替换函数将由 KUNIT_STATIC_STUB_REDIRECT() 调用,然后它将从该函数返回。如果调用方不在 KUnit 上下文中,则该函数将照常继续执行。

int real_func(int n)
{
        KUNIT_STATIC_STUB_REDIRECT(real_func, n);
        return 0;
}

int replacement_func(int n)
{
        return 42;
}

void example_test(struct kunit *test)
{
        kunit_activate_static_stub(test, real_func, replacement_func);
        KUNIT_EXPECT_EQ(test, real_func(1), 42);
}

示例

kunit_activate_static_stub

kunit_activate_static_stub (test, real_fn_addr, replacement_addr)

使用静态存根替换函数。

参数

test

指向当前测试的“struct kunit”测试上下文的指针。

real_fn_addr

要替换的函数的地址。

replacement_addr

要替换为的函数的地址。

描述

激活后,从此测试内(即使是间接调用)对 real_fn_addr 的调用将改为调用 replacement_addr。 real_fn_addr 指向的函数必须以 KUNIT_STATIC_STUB_REDIRECT() 中的静态存根序言开始才能工作。 real_fn_addr 和 replacement_addr 必须具有相同的类型。

可以使用 kunit_deactivate_static_stub() 再次禁用重定向。

void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr)

禁用函数重定向

参数

struct kunit *test

指向当前测试的“struct kunit”测试上下文的指针。

void *real_fn_addr

不再重定向的函数的地址

描述

停用使用 kunit_activate_static_stub() 配置的重定向。在此函数返回后,对 real_fn_addr() 的调用将执行原始的 real_fn,而不是任何先前配置的替换。