logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Using-Wl-wrap-for-mocking-in-C.md (3256B)


  1. ---
  2. date: 2016-07-19
  3. # vim: tw=80
  4. title: Using -Wl,--wrap for mocking in C
  5. layout: post
  6. tags: [C]
  7. ---
  8. One of the comforts I've grown used to in higher level languages when testing
  9. my code is mocking. The idea is that in order to test some code in isolation,
  10. you should "mock" the behavior of things it depends on. Let's see a (contrived)
  11. example:
  12. ```c
  13. int read_to_end(FILE *f, char *buf) {
  14. int r = 0, l;
  15. while (!feof(f)) {
  16. l = fread(buf, 1, 256, f);
  17. r += l;
  18. buf += l;
  19. }
  20. return r;
  21. }
  22. ```
  23. If we want to test this function without mocking, we would need to actually open
  24. a specially crafted file and provide a `FILE*` to the function. However, with
  25. the linker `--wrap` flag, we can define a wrapper function. Using `-Wl,[flag]`
  26. in your C compiler command line will pass `[flag]` to the linker. Gold (GNU) and
  27. lld (LLVM) both support the wrap flag, which specifies a function to be
  28. "wrapped". If I use `-Wl,--wrap=fread`, then the code above will be compiled
  29. like so:
  30. ```c
  31. int read_to_end(FILE *f, char *buf) {
  32. int r = 0, l;
  33. while (!feof(f)) {
  34. l = __wrap_fread(buf, 1, 256, f);
  35. r += l;
  36. buf += l;
  37. }
  38. return r;
  39. }
  40. ```
  41. And if I add `-Wl,--wrap=feof` we'll get this:
  42. ```c
  43. int read_to_end(FILE *f, char *buf) {
  44. int r = 0, l;
  45. while (!__wrap_feof(f)) {
  46. l = __wrap_fread(buf, 1, 256, f);
  47. r += l;
  48. buf += l;
  49. }
  50. return r;
  51. }
  52. ```
  53. Now, we can define some functions that do the behavior we need to test instead
  54. of invoking fread directly:
  55. ```c
  56. int feof_return_value = 0;
  57. int __wrap_feof(FILE *f) {
  58. assert(f == (FILE *)0x1234);
  59. return feof_return_value;
  60. }
  61. void test_read_to_end_eof() {
  62. // ...
  63. feof_return_value = 1;
  64. read_to_end((FILE *)0x1234, buf);
  65. // ...
  66. }
  67. ```
  68. Using `--wrap` also conveniently defines `__real_feof` and `__real_fread` if we
  69. need them.
  70. Unfortunately, you can't have two different wrappers for the same function in
  71. an executable. This could lead to having to write several executables for each,
  72. or making your wrapper function smart enough to have several configurable
  73. outcomes.
  74. Eventually I intend to write my own test framework for C, which will use
  75. wrappers to support mocking. I want wrappers to be done automatically and have
  76. it behave something like this:
  77. ```c
  78. static int fake_fread(void *ptr, size_t size,
  79. size_t nmemb, FILE *stream) {
  80. const char *hello = "Hello world!";
  81. memcpy(ptr, hello, strlen(hello) + 1);
  82. return strlen(hello) + 1;
  83. }
  84. void test_read_to_end() {
  85. FILE *test = (FILE *)0x1234;
  86. char *buffer = char[1024];
  87. mock_t *mock_feof = configure_mock(feof, "p");
  88. mock_feof->call(0)->returns(0);
  89. mock_feof->returns(1);
  90. // pzzp is pointer, size_t, size_t, pointer
  91. // Tells us what the fread arguments look like
  92. mock_t *mock_fread = configure_mock(fread, "pzzp");
  93. mock_fread->exec(fake_fread);
  94. read_to_end(test, buffer);
  95. assert(mock_feof->call_count == 2);
  96. assert((FILE*)mock_feof->call(0)->args(0) == test);
  97. assert(mock_fread->call_count == 1);
  98. assert((FILE*)mock_fread->call(0)->args(0) == buffer);
  99. assert((FILE*)mock_fread->call(0)->args(3) == test);
  100. assert(strcmp(buffer, "Hello world!") == 0);
  101. }
  102. ```