代码之家  ›  专栏  ›  技术社区  ›  Robert S. Barnes Antoni

关于模拟系统调用的建议

  •  29
  • Robert S. Barnes Antoni  · 技术社区  · 15 年前

    我有一个班级 getaddrinfo

    6 回复  |  直到 15 年前
        1
  •  27
  •   Kaleb Pederson    15 年前

    在这种情况下,你不需要嘲笑 getaddrinfo

    选项1:要测试的子类

    因为你已经在一个类中有了你的对象,你可以用子类来测试。例如,假设以下是您的实际类:

    class DnsClass {
        int lookup(...);
    };
    
    int DnsClass::lookup(...) {
        return getaddrinfo(...);
    }
    

    然后,为了进行测试,您可以这样子类化:

    class FailingDnsClass {
        int lookup(...) { return 42; }
    };
    

    现在可以使用 FailingDnsClass 子类生成错误,但仍然验证当发生错误情况时,所有操作都正确。在这种情况下,依赖注入通常是您的朋友。

    选项2:使用连接接缝

    Working Effectively with Legacy Code .

    测试.cpp:

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <iostream>
    
    int main(void)
    {
            int retval = getaddrinfo(NULL, NULL, NULL, NULL);
            std::cout << "RV:" << retval << std::endl;
            return retval;
    }
    

    库.cpp:

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    
    int getaddrinfo(const char *node, const char *service,
            const struct addrinfo *hints, struct addrinfo **res
            )
    {
            return 42;
    }
    

    然后进行测试:

    $ g++ test.cpp lib.cpp -o test
    $ ./test 
    RV:42
    
        2
  •  13
  •   Patrick    15 年前

    查找“依赖注入”的模式。

    在实际代码中,调用方传递接口的实现,该接口将接口的虚拟方法“getaddrinfo”映射到real::getaddrinfo函数。

    "Working effectively with legacy code"

        3
  •  10
  •   deft_code    15 年前

    3个选项

    . 使用gnu链接器的模仿功能 --wrap

    ld --wrap=getaddrinfo /*the rest of the link line*/
    or
    g++ -Wl,--wrap=getaddrinfo /* the rest of the build line*/
    
    // this in the unit tests.
    bool g_getaddrinfo_use_real = true;
    int g_getaddrinfo_ret = -1;
    int g_getaddrinfo_errno = something;
    int __wrap_getaddrinfo( const char *node, const char *service,
                            const struct addrinfo *hints,
                            struct addrinfo **res )
    {
       if( g_getaddrinfo_use_real )
          return __real_getaddrinfo(node,service,hints,res);
    
       errno = g_getaddrinfo_errno;
       return g_getaddrinfo_ret;
    }
    

    2 . 定义自己的getaddrinfo并将其静态链接到测试应用程序。这只有在libc是动态链接的情况下才有效,这在99%的情况下都是正确的。这种方法也有一个缺点:在单元测试应用程序中永久禁用真正的getaddrinfo,但是实现起来非常简单。

    int g_getadderinfo_ret = -1;
    int g_getaddrinfo_errno = something;
    int getaddrinfo( const char *node, const char *service,
                     const struct addrinfo *hints,
                     struct addrinfo **res )
    {
       errno = g_getaddrinfo_errno
       return g_getaddrinfo_ret;
    }
    

    printf , open 等等)。

    typedef (*getaddrinfo_func_type)( const char *node, const char *service,
                                   const struct addrinfo *hints,
                                   struct addrinfo **res );
    
    getaddrinfo_func_type g_getaddrinfo_func;
    
    int getaddrinfo( const char *node, const char *service,
                     const struct addrinfo *hints,
                     struct addrinfo **res )
    {
       return g_getaddrinfo_func( node, service, hints, res )
    }
    
    int g_mock_getadderinfo_ret = -1;
    int g_mock_getaddrinfo_errno = something;
    int mock_getaddrinfo( const char *node, const char *service,
                          const struct addrinfo *hints,
                          struct addrinfo **res )
    {
       errno = g_mock_getaddrinfo_errno;
       return g_mock_getaddrinfo_ret;
    }
    
    // use the original
    g_getaddrinfo_func = dlsym(RTDL_NEXT, "getaddrinfo");
    
    // use the mock version
    g_getaddrinfo_func = &mock_getaddrinfo;
    
        4
  •  2
  •   Steve Lorimer    10 年前

    你可以使用ELF系统 elf_hook

    • 创建包含测试代码的共享库
    • 创建动态加载共享库的测试( dlopen
    • 将要模拟的符号重定向到测试函数

    elf炣hook具有以下签名:

    void* elf_hook(char const* library_filename, 
                   void const* library_address, 
                   char const* function_name, 
                   void const* substitution_address);
    

    你可以这样使用:

    int hooked_getaddrinfo(const char* node, 
                           const char* service,
                           const struct addrinfo* hints,
                           struct addrinfo** res)
    {
        return 42;
    }
    
    const char* lib_path = "path/to/library/under/test.so";
    void* lib_handle = dlopen(lib_path, RTLD_LAZY);
    
    elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), "getaddrinfo", hooked_getaddrinfo);
    

    有没有打电话给 getaddrinfo 从正在测试的库中调用 hooked_getaddrinfo

    一篇综合性的文章由elf_nuHook的作者,Anthony Shoumikhin,是 here

        5
  •  1
  •   Edward Strange    15 年前

    虽然技术上可行,但我不认为这是可行的。您必须能够替换该函数的实现,而您可能无法并且仍然链接到系统上的标准库。

    你应该做的是打电话给中介。然后,您可以在测试期间模拟中介,并将其转发到生产中的实际函数。您甚至可以考虑创建一个类,该类与此函数和其他类似函数交互,并为您的程序提供一个更通用的接口。这个类在大多数时候除了转发调用之外不会做任何事情,但是在测试期间它可以被有效地模仿,并且你可以测试任何使用它的东西。

        6
  •  0
  •   Robin Nicholson    7 年前

    免责声明:我写的是ELFspy。

    例子:

    int myaddrinfo(const char* node, const char* service,
                       const struct addrinfo* hints,
                       struct addrinfo** res)
    {
      return EAI_NODATA;
    }
    int main(int argv, char** argc)
    {
        spy::initialise(argc, argv);
        auto gai_spy = SPY(&getaddrinfo);
        auto gai_fake = spy::fake(gai_spy, &myaddrinfo);
        ...
    }
    

    如果需要,可以调用原始getaddrinfo,如下所示:

    gai_spy.invoke_real(node, service, hints, res);
    

    您的代码必须使用-fPIC作为独立于位置的代码进行编译,这样才能正常工作。

    关于模拟时间(time_t*)示例中的更多详细信息可在此处找到: https://github.com/mollismerx/elfspy/wiki/Example-03:-Faking-time