├── LICENSE ├── Makefile ├── README.md ├── inc └── toaster.h └── src ├── test.c └── toaster.c /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 aeyakovenko@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (c) 2017 aeyakovenko@gmail.com 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | 22 | all: 23 | 24 | OBJS+=out/toaster.o 25 | out/toaster.o:src/toaster.c 26 | 27 | CEXES+=cov/test 28 | cov/test:src/test.c out/toaster.o 29 | 30 | COVS+=cov/test.c.cov 31 | cov/test.c.cov:cov/test 32 | 33 | ############################## 34 | #rules 35 | all:$(OBJS) $(COVS) 36 | 37 | clean: 38 | rm -rf out cov *.gcno *.gcda *.gcov 39 | 40 | CFLAGS+=-Iinc -fPIC -g -Wall -Werror -O3 -std=c99 41 | 42 | DEP_FLAGS=-MMD -MP -MF $(@:%=%.d) 43 | 44 | OBJ_DEPS=$(OBJS:%=%.d) 45 | -include $(OBJ_DEPS) 46 | 47 | $(OBJS): 48 | @mkdir -p $(@D) 49 | $(CC) -o $@ -c $(filter %.c, $^) $(CFLAGS) $(DEP_FLAGS) 50 | 51 | EXE_DEPS=$(EXES:%=%.d) 52 | -include $(EXE_DEPS) 53 | 54 | $(EXES): 55 | @mkdir -p $(@D) 56 | $(CC) -o $@ $^ $(CFLAGS) $(DEP_FLAGS) 57 | 58 | DLL_DEPS=$(DLLS:%=%.d) 59 | -include $(DLL_DEPS) 60 | 61 | $(DLLS): 62 | mkdir -p $(@D) 63 | $(CC) -o $@ $^ $(CFLAGS) -shared -ldl $(DEP_FLAGS) 64 | 65 | export GCOV_PREFIX=cov 66 | export GCOV_PREFIX_STRIP=$(words $(subst /, ,$(PWD))) 67 | 68 | VALGRIND=$(shell which valgrind) 69 | ifeq (,$(VALGRIND)) 70 | VALGRINDCMD=# 71 | else 72 | VALGRINDCMD=$(VALGRIND) --leak-check=yes --error-exitcode=5 -q 73 | endif 74 | GCOV:=gcov 75 | 76 | $(COVS): 77 | @mkdir -p $(@D) 78 | $(VALGRINDCMD) $< 79 | mv *.gcno $(@D)/ || echo ok 80 | $(GCOV) -r -c -b -o $(@D) $(notdir $(@:%.cov=%)) | tee $<.cov.out 81 | mv *.gcov $(@D)/ || echo ok 82 | @grep "Branches executed:100" $<.cov.out 83 | @grep "Lines executed:100" $<.cov.out 84 | touch $@ 85 | 86 | CEXE_DEPS=$(CEXES:%=%.d) 87 | -include $(CEXE_DEPS) 88 | 89 | $(CEXES): 90 | @mkdir -p $(@D) 91 | $(CC) -o $@ $(filter-out %.h, $^) $(DEP_FLAGS) $(LD_FLAGS) $(CFLAGS) -coverage -ldl -DTOASTER 92 | 93 | $$%:;@$(call true)$(info $(call or,$$$*)) 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Incremental Failure Injection in C 2 | ================================== 3 | 4 | Most of the challenge in writing bug free C code is dealing with safe error handling. This technique encapsulates all the error handling into a single pattern that can be used to programatically inject errors into every possible error condition. This approach is not as thorough as covering every branch by testing a large input space, but it allows a small number of tests to cover a large set of common programming bugs, especially when combined with valgrind. 5 | 6 | Just run make to try it. 7 | 8 | ```bash 9 | $ make 10 | cc -o cov/test src/test.c out/toaster.o -MMD -MP -MF cov/test.d -Iinc -fPIC -g -Wall -Werror -O3 -std=c99 -coverage -ldl -DTOASTER 11 | /usr/bin/valgrind --leak-check=yes --error-exitcode=5 -q cov/test 12 | src/toaster.c:66:toaster:test count: 0 13 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 14 | src/test.c:81:toaster:inject:!unix_sock_create_and_bind("foo", &a) 15 | ``` 16 | ```bash 17 | src/toaster.c:66:toaster:test count: 1 18 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 19 | src/test.c:51:toaster:mock failure: socket 20 | src/test.c:60:toaster:call:s >= 0 21 | src/test.c:60:toaster:inject:s >= 0 22 | src/test.c:81:toaster:fail:!unix_sock_create_and_bind("foo", &a) 23 | ``` 24 | ```bash 25 | src/toaster.c:66:toaster:test count: 2 26 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 27 | src/test.c:60:toaster:call:s >= 0 28 | src/test.c:60:toaster:inject:s >= 0 29 | src/test.c:81:toaster:fail:!unix_sock_create_and_bind("foo", &a) 30 | ``` 31 | ```bash 32 | src/toaster.c:66:toaster:test count: 3 33 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 34 | src/test.c:60:toaster:call:s >= 0 35 | src/test.c:60:toaster:pass:s >= 0 36 | src/test.c:62:toaster:call:sz <= sizeof(addr.sun_path) 37 | src/test.c:62:toaster:inject:sz <= sizeof(addr.sun_path) 38 | src/test.c:81:toaster:fail:!unix_sock_create_and_bind("foo", &a) 39 | ``` 40 | ... 41 | ```bash 42 | gcov -r -c -b -o cov test.c | tee cov/test.cov.out 43 | File 'src/test.c' 44 | Lines executed:100.00% of 49 45 | Branches executed:100.00% of 52 46 | Taken at least once:84.62% of 52 47 | Calls executed:96.67% of 30 48 | Creating 'test.c.gcov' 49 | ``` 50 | 51 | How does it work? 52 | ================= 53 | 54 | Single Exit Point and Error Handling Macros 55 | ------------------------------------------- 56 | 57 | Many C functions are written with multiple exit points, and a wide range of error handling approaches. If all the error handling code is standardized into a single pattern, it becomes easy to follow and programatically control. Here is a simple example of an error handling macro 58 | 59 | ```C 60 | /** 61 | * if expr is false, goto the CHECK(err) label. Set err to -1 if it's 0. 62 | */ 63 | #define TEST(err, expr) \ 64 | if(!(expr)) {\ 65 | if(!err) {\ 66 | err = -1;\ 67 | }\ 68 | goto CHECK(err); \ 69 | } 70 | } while(0) 71 | 72 | /** 73 | * CHECK(err) label. Should alwasy be at the end of the function before cleanup routines. 74 | */ 75 | #define CHECK(err) __ ## err ## _test_check 76 | ``` 77 | 78 | The `TEST(err, expr)` macro tests if `expr` is true, if not it will `goto` to a label that is generated using the `err` variable name. 79 | 80 | The `CHECK(err)` macro generates the `goto` label out of the `err` name. 81 | 82 | Example 83 | ------- 84 | 85 | Simple function to create a unix tcp socket and bind to it. 86 | 87 | ```C 88 | int unix_sock_create_and_bind(const char *path, int *fd) { 89 | int err = 0; 90 | struct sockaddr_un addr = {}; 91 | size_t sz = strlen(path); 92 | int s = socket(AF_UNIX, SOCK_DGRAM, 0); 93 | TEST(err, s >= 0); 94 | addr.sun_family = AF_UNIX; 95 | TEST(err, sz <= sizeof(addr.sun_path)); 96 | memmove(addr.sun_apth, path, sz); 97 | TEST(err, !bind(s, (struct sockaddr*)&addr, sizeof(addr))); 98 | *fd = s; 99 | s = -1; 100 | CHECK(err): 101 | if(s != -1) { 102 | close(s); 103 | } 104 | return err; 105 | } 106 | ``` 107 | 108 | The function has a single return statement at the end, returning the `err` variable. Everything after the `CHECK(err)` label is cleanup code. Follwing this pattern makes it really easy to spot the exit points in the function (there is only one at the end), the cleanup code, and all the possible error conditions. 109 | 110 | Test with Failure Injection 111 | ---------------------------- 112 | 113 | A simple counter can then be used to incrementally inject errors. This is a library wrapping a counter for the maximum number of successes each test iteration will allow. 114 | 115 | ```C 116 | /** 117 | * TOASTER, a simple library for incremental failure injection 118 | */ 119 | 120 | /** 121 | * @retval, return 0 only if check passes 122 | */ 123 | int toaster_check(void); 124 | 125 | /** 126 | * run the test from 0 to `max` number checks passing until `test` returns 0 127 | * @retval 0, if test returned 0 128 | */ 129 | int toaster_run_max(int max, int (*test)(void)); 130 | ``` 131 | 132 | Each time `toaster_check` is called its counter is decremented. When the `toaster_check` counter hits `0`, `toaster_check` will return -1 as a failure. `toaster_run_max` runs the `test` in a loop, with the `toaster_check` counter set from 0 up to `max` until the `test` succeeds. 133 | 134 | ```C 135 | int gcnt; 136 | int gset; 137 | 138 | int toaster_check(void) { 139 | if(gset && --gcnt < 0) { 140 | return -1; 141 | } 142 | return 0; 143 | } 144 | void toaster_set(int cnt) { 145 | gcnt = cnt; 146 | gset = 1; 147 | } 148 | void toaster_end(void) { 149 | gcnt = 0; 150 | gset = 0; 151 | } 152 | int toaster_run_max(int max, int (*test)(void)) { 153 | int i; 154 | int err = -1; 155 | for(i = 0; i <= max && err != 0; ++i) { 156 | TOASTER_LOG("test count: %d", i); 157 | //set the toaster_check counter to i 158 | toaster_set(i); 159 | err = test(); 160 | } 161 | toaster_end(); 162 | return err; 163 | } 164 | ``` 165 | 166 | Now we can inject the `toaster_check` calls into every call to the `TEST` macro. 167 | 168 | ```C 169 | #ifdef TOASTER 170 | #define TOASTER_INJECT_FAILURE(err, expr) \ 171 | if(0 != toaster_check()) {\ 172 | if(!err) {\ 173 | err = -1;\ 174 | }\ 175 | TOASTER_LOG("inject:%s", #expr); \ 176 | goto CHECK(err); \ 177 | } else 178 | #else 179 | #define TOASTER_INJECT_FAILURE(err, expr) 180 | #endif 181 | 182 | #define TEST(err, expr) \ 183 | do {\ 184 | TOASTER_LOG("call:%s", #expr); \ 185 | TOASTER_INJECT_FAILURE(err, expr) \ 186 | if(!(expr)) {\ 187 | if(!err) {\ 188 | err = -1;\ 189 | }\ 190 | TOASTER_LOG("fail:%s", #expr); \ 191 | goto CHECK(err); \ 192 | } else {\ 193 | TOASTER_LOG("pass:%s", #expr); \ 194 | }\ 195 | } while(0) 196 | 197 | #define CHECK(err) __ ## err ## _test_check 198 | ``` 199 | 200 | Since the test runs with an incremental number of `TEST` macros passing we are able to verify that our `unix_sock_create_and_bind` function can handle and error on line `TEST(err, s >= 0);`, and `TEST(err, !bind(s, &addr, sizeof(addr)));`. 201 | 202 | Mock out external APIs 203 | ---------------------- 204 | GNUs dlfcn defines an `RTLD_NEXT` macro that allows you to load the next symbol in the symbol list for a particular api. So you can write tests that override the default implementation of an externally linked api. This is functionally equivalent to using `LD_PRELOAD`. 205 | 206 | ```C 207 | #define _GNU_SOURCE 208 | #include 209 | 210 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 211 | int (*real)(int, const struct sockaddr *, socklen_t) = dlsym(RTLD_NEXT, "bind"); 212 | if(!toaster_check()) { 213 | return real(sockfd, addr, addrlen); 214 | } 215 | TOASTER_LOG("mock failure: bind"); 216 | return -1; 217 | } 218 | ``` 219 | Since `bind` was linked into the main program, that definition will be used by default. I can use `RTLD_NEXT` to find the real definition and programatically inject a failure into that call. 220 | 221 | Use valgrind! 222 | ------------- 223 | 224 | ```bash 225 | valgrind --leak-check=yes --error-exitcode=5 -q ./test 226 | ``` 227 | 228 | Valgrind is a great tool that will catch leaks and uninitialized memory access errors. In combination with incremental failure injection, valgrind will spot any tests that have leaked memory during a simulated failure. Such as a `free` of an uninitialized pointer that never got set during an error. 229 | 230 | Running the Example 231 | -------------------- 232 | 233 | Simple `make` should run this code on most systems that have `gcc`, `gcov` and optionally `valgrind` installed. To build with specific versions of `gcc` and `gcov` you can run `make CC=gcc-6 GCOV=gcov-6`. 234 | 235 | ```bash 236 | $ make 237 | Lines executed:100.00% of 49 238 | Branches executed:100.00% of 52 239 | Taken at least once:84.62% of 52 240 | Calls executed:96.67% of 30 241 | Creating 'test.c.gcov' 242 | ``` 243 | 244 | Each iteration of the test will inject a failure at the next failure point. 245 | 246 | ```bash 247 | src/toaster.c:66:toaster:test count: 3 248 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 249 | src/test.c:60:toaster:call:s >= 0 250 | src/test.c:60:toaster:pass:s >= 0 251 | src/test.c:62:toaster:call:sz <= sizeof(addr.sun_path) 252 | src/test.c:62:toaster:inject:sz <= sizeof(addr.sun_path) 253 | ``` 254 | 255 | ```bash 256 | src/toaster.c:66:toaster:test count: 4 257 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 258 | src/test.c:60:toaster:call:s >= 0 259 | src/test.c:60:toaster:pass:s >= 0 260 | src/test.c:62:toaster:call:sz <= sizeof(addr.sun_path) 261 | src/test.c:62:toaster:pass:sz <= sizeof(addr.sun_path) 262 | src/test.c:64:toaster:call:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 263 | src/test.c:64:toaster:inject:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 264 | ``` 265 | 266 | ```bash 267 | src/toaster.c:66:toaster:test count: 5 268 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 269 | src/test.c:60:toaster:call:s >= 0 270 | src/test.c:60:toaster:pass:s >= 0 271 | src/test.c:62:toaster:call:sz <= sizeof(addr.sun_path) 272 | src/test.c:62:toaster:pass:sz <= sizeof(addr.sun_path) 273 | src/test.c:64:toaster:call:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 274 | src/test.c:41:toaster:mock failure: bind 275 | ``` 276 | 277 | Until the test eventually passes 278 | 279 | ```bash 280 | src/toaster.c:66:toaster:test count: 17 281 | src/test.c:81:toaster:call:!unix_sock_create_and_bind("foo", &a) 282 | src/test.c:60:toaster:call:s >= 0 283 | src/test.c:60:toaster:pass:s >= 0 284 | src/test.c:62:toaster:call:sz <= sizeof(addr.sun_path) 285 | src/test.c:62:toaster:pass:sz <= sizeof(addr.sun_path) 286 | src/test.c:64:toaster:call:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 287 | src/test.c:64:toaster:pass:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 288 | src/test.c:81:toaster:pass:!unix_sock_create_and_bind("foo", &a) 289 | src/test.c:82:toaster:call:!unix_sock_create_and_bind("bar", &b) 290 | src/test.c:60:toaster:call:s >= 0 291 | src/test.c:60:toaster:pass:s >= 0 292 | src/test.c:62:toaster:call:sz <= sizeof(addr.sun_path) 293 | src/test.c:62:toaster:pass:sz <= sizeof(addr.sun_path) 294 | src/test.c:64:toaster:call:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 295 | src/test.c:64:toaster:pass:!bind(s, (struct sockaddr *)&addr, sizeof(addr)) 296 | src/test.c:82:toaster:pass:!unix_sock_create_and_bind("bar", &b) 297 | src/test.c:87:toaster:call:strlen(send) == sendto(a, send, strlen(send), 0, (struct sockaddr*)&addr, sizeof(addr)) 298 | src/test.c:87:toaster:pass:strlen(send) == sendto(a, send, strlen(send), 0, (struct sockaddr*)&addr, sizeof(addr)) 299 | src/test.c:89:toaster:call:0 < recvfrom(b, recv, sizeof(recv), 0, (struct sockaddr*)&addr, &len) 300 | src/test.c:89:toaster:pass:0 < recvfrom(b, recv, sizeof(recv), 0, (struct sockaddr*)&addr, &len) 301 | src/test.c:91:toaster:call:len < sizeof(addr) 302 | src/test.c:91:toaster:pass:len < sizeof(addr) 303 | src/test.c:92:toaster:call:0 == memcmp(addr.sun_path, "foo", strlen("foo")) 304 | src/test.c:92:toaster:pass:0 == memcmp(addr.sun_path, "foo", strlen("foo")) 305 | src/test.c:93:toaster:call:0 == memcmp(send, recv, strlen(send)) 306 | src/test.c:93:toaster:pass:0 == memcmp(send, recv, strlen(send)) 307 | ``` 308 | 309 | The difference between incremental failure injection and no failure injection can be seen by modifying the `main` function to look like 310 | 311 | ```C 312 | 313 | int main(int _argc, char * const _argv[]) { 314 | assert(0 == test_talk()); 315 | return 0; 316 | } 317 | ``` 318 | 319 | The resulting output should be 320 | 321 | ```bash 322 | $ make 323 | Lines executed:89.80% of 49 324 | Branches executed:100.00% of 52 325 | Taken at least once:50.00% of 52 326 | Calls executed:86.67% of 30 327 | Creating 'test.c.gcov' 328 | ``` 329 | 330 | 331 | -------------------------------------------------------------------------------- /inc/toaster.h: -------------------------------------------------------------------------------- 1 | /** 2 | * toaster.h 3 | * 4 | * Copyright (c) 2017 aeyakovenko@gmail.com 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | #ifndef TOASTER_H 24 | #define TOASTER_H 25 | 26 | #ifdef TOASTER_SHOW_LOG 27 | #include 28 | #endif 29 | 30 | #define TOASTER_NOOP (void)0 31 | 32 | #define TOASTER_PRINTLN(format, ...) \ 33 | fprintf(stderr, __FILE__ ":%d:" format "\n", __LINE__, ##__VA_ARGS__) 34 | 35 | #ifdef TOASTER_SHOW_LOG 36 | #define TOASTER_LOG(format, ...) TOASTER_PRINTLN("toaster:" format, ##__VA_ARGS__) 37 | #else 38 | #define TOASTER_LOG(format, ...) TOASTER_NOOP 39 | #endif 40 | 41 | #ifdef TOASTER 42 | #define TOASTER_INJECT_FAILURE(err, expr) \ 43 | if(0 != toaster_check()) {\ 44 | if(!err) {\ 45 | err = -1;\ 46 | }\ 47 | TOASTER_LOG("inject:%s", #expr); \ 48 | goto CHECK(err); \ 49 | } else 50 | #else 51 | #define TOASTER_INJECT_FAILURE(err, expr) 52 | #endif 53 | 54 | #define TEST(err, expr) \ 55 | do {\ 56 | TOASTER_LOG("call:%s", #expr); \ 57 | TOASTER_INJECT_FAILURE(err, expr) \ 58 | if(!(expr)) {\ 59 | if(!err) {\ 60 | err = -1;\ 61 | }\ 62 | TOASTER_LOG("fail:%s", #expr); \ 63 | goto CHECK(err); \ 64 | } else {\ 65 | TOASTER_LOG("pass:%s", #expr); \ 66 | }\ 67 | } while(0) 68 | 69 | #define CHECK(err) __ ## err ## _test_check 70 | 71 | int toaster_check(void); 72 | void toaster_set(int cnt); 73 | int toaster_get(); 74 | void toaster_end(void); 75 | int toaster_run_max(int max, int (*test)(void)); 76 | int toaster_run_range(int min, int max, int (*test)(void)); 77 | int toaster_run(int (*test)(void)); 78 | 79 | 80 | #endif //TOASTER_H 81 | -------------------------------------------------------------------------------- /src/test.c: -------------------------------------------------------------------------------- 1 | /** 2 | * test.c 3 | * 4 | * Copyright (c) 2017 aeyakovenko@gmail.com 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | #define _GNU_SOURCE 25 | #define TOASTER_SHOW_LOG 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "toaster.h" 35 | /** mock for bind */ 36 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 37 | int (*real)(int, const struct sockaddr *, socklen_t) = dlsym(RTLD_NEXT, "bind"); 38 | if(!toaster_check()) { 39 | return real(sockfd, addr, addrlen); 40 | } 41 | TOASTER_LOG("mock failure: bind"); 42 | return -1; 43 | } 44 | 45 | /** mock for socket*/ 46 | int socket(int domain, int type, int protocol) { 47 | int (*real)(int domain, int type, int protocol) = dlsym(RTLD_NEXT, "socket"); 48 | if(!toaster_check()) { 49 | return real(domain, type, protocol); 50 | } 51 | TOASTER_LOG("mock failure: socket"); 52 | return -1; 53 | } 54 | 55 | int unix_sock_create_and_bind(const char *path, int *fd) { 56 | int err = 0; 57 | struct sockaddr_un addr = {}; 58 | size_t sz = strlen(path); 59 | int s = socket(AF_UNIX, SOCK_DGRAM, 0); 60 | TEST(err, s >= 0); 61 | addr.sun_family = AF_UNIX; 62 | TEST(err, sz <= sizeof(addr.sun_path)); 63 | memmove(addr.sun_path, path, sz); 64 | TEST(err, !bind(s, (struct sockaddr *)&addr, sizeof(addr))); 65 | *fd = s; 66 | s = -1; 67 | CHECK(err): 68 | if(s != -1) { 69 | close(s); 70 | } 71 | return err; 72 | } 73 | 74 | int test_talk(void) { 75 | int err = 0; 76 | int a = -1, b = -1; 77 | socklen_t len; 78 | struct sockaddr_un addr; 79 | const char *send = "hello world"; 80 | char recv[sizeof("hello world")] = {}; 81 | TEST(err, !unix_sock_create_and_bind("foo", &a)); 82 | TEST(err, !unix_sock_create_and_bind("bar", &b)); 83 | 84 | addr.sun_family = AF_UNIX; 85 | memmove(addr.sun_path, "bar", strlen("bar") + 1); 86 | TEST(err, strlen(send) == sendto(a, send, strlen(send), 0, 87 | (struct sockaddr*)&addr, sizeof(addr))); 88 | len = sizeof(addr); 89 | TEST(err, 0 < recvfrom(b, recv, sizeof(recv), 0, (struct sockaddr*)&addr, &len)); 90 | 91 | TEST(err, len <= sizeof(addr)); 92 | TEST(err, 0 == memcmp(addr.sun_path, "foo", strlen("foo"))); 93 | TEST(err, 0 == memcmp(send, recv, strlen(send))); 94 | 95 | CHECK(err): 96 | if(-1 != a) { 97 | close(a); 98 | } 99 | if(-1 != b) { 100 | close(b); 101 | } 102 | /** cleanup socket files on exit */ 103 | unlink("foo"); 104 | unlink("bar"); 105 | return err; 106 | } 107 | 108 | int main(int _argc, char * const _argv[]) { 109 | assert(0 == toaster_run_max(100, test_talk)); 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /src/toaster.c: -------------------------------------------------------------------------------- 1 | /** 2 | * toaster.c 3 | * 4 | * Copyright (c) 2017 aeyakovenko@gmail.com 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | #define _GNU_SOURCE 24 | #define TOASTER_SHOW_LOG 25 | #include "toaster.h" 26 | 27 | static int gcnt; 28 | static int gset; 29 | 30 | int toaster_check(void) { 31 | if(gset && --gcnt < 0) { 32 | return -1; 33 | } 34 | return 0; 35 | } 36 | 37 | void toaster_set(int cnt) { 38 | gcnt = cnt; 39 | gset = 1; 40 | } 41 | 42 | int toaster_get(void) { 43 | if(gset) { 44 | return gcnt; 45 | } 46 | return -1; 47 | } 48 | 49 | void toaster_end(void) { 50 | gcnt = 0; 51 | gset = 0; 52 | } 53 | 54 | int toaster_run(int (*test)(void)) { 55 | return test(); 56 | } 57 | 58 | int toaster_run_max(int max, int (*test)(void)) { 59 | return toaster_run_range(0, max, test); 60 | } 61 | 62 | int toaster_run_range(int min, int max, int (*test)(void)) { 63 | int i; 64 | int err = -1; 65 | for(i = min; i <= max && err != 0; ++i) { 66 | TOASTER_LOG("test count: %d", i); 67 | toaster_set(i); 68 | err = test(); 69 | } 70 | toaster_end(); 71 | return err; 72 | } 73 | --------------------------------------------------------------------------------