├── .appveyor.yml ├── .clang-format ├── .codecov.yml ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── Makefile.unix ├── Makefile.win32 ├── README.adoc ├── _config.yml ├── build.cmd ├── build.sh ├── convey.c ├── convey.h ├── convey_test.c ├── demo.c ├── screenshot_1.png └── screenshot_2.png /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | environment: 3 | VS_VERSION: 14.0 4 | TERM: xterm 5 | build_script: 6 | - cmd: IF NOT %VS_VERSION% == NONE call "C:/Program Files (x86)/Microsoft Visual Studio %VS_VERSION%/Common7/Tools/vsvars32.bat" 7 | - cmd: build.cmd clean 8 | - cmd: build.cmd build 9 | - cmd: build.cmd test -v 10 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: WebKit 2 | UseTab: ForIndentation 3 | IndentWidth: 8 4 | ColumnLimit: 79 5 | AlignConsecutiveAssignments: true 6 | AlignConsecutiveDeclarations: true 7 | AlignTrailingComments: true 8 | AlignEscapedNewlinesLeft: true 9 | PointerAlignment: Right 10 | ForEachMacros: ['NNI_LIST_FOREACH'] 11 | AlwaysBreakAfterReturnType: TopLevelDefinitions 12 | SpaceAfterCStyleCast: true 13 | AllowShortFunctionsOnASingleLine: Inline 14 | BreakBeforeBinaryOperators: None 15 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 50..85 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.gcno 3 | *.gcda 4 | *.obj 5 | *.o 6 | convey_test 7 | convey_test.dSYM 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | matrix: 4 | include: 5 | - os: linux 6 | compiler: gcc 7 | env: TEST=test 8 | - os: linux 9 | compiler: clang 10 | env: TEST=test 11 | - os: linux 12 | compiler: gcc 13 | env: TEST=coverage 14 | - os: osx 15 | compiler: clang 16 | env: TEST=test 17 | script: 18 | - ./build.sh clean 19 | - ./build.sh build 20 | - ./build.sh ${TEST} 21 | - bash <(curl -s https://codecov.io/bash) -x gcov || echo "Codecov did not collect coverage" 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), 3 | to deal in the Software without restriction, including without limitation 4 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom 6 | the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included 9 | in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 14 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 16 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 17 | IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /Makefile.unix: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Garrett D'Amore 3 | # 4 | # This software is supplied under the terms of the MIT License, a 5 | # copy of which should be located in the distribution where this 6 | # file was obtained (LICENSE.txt). A copy of the license may also be 7 | # found online at https://opensource.org/licenses/MIT. 8 | # 9 | 10 | # 11 | # Simple Makefile - for UNIX/Linux systems. 12 | # 13 | # The way the tests work is that you have to compile both your own .c 14 | # (containing your test code) and the test.c file that contains the 15 | # convey framework; you'll also need to have the header files in 16 | # the same directory, or located on the include search path. 17 | # 18 | all: run_tests 19 | 20 | # Uncomment if you don't have pthreads. 21 | # CDEFS = CONVEY_NO_THREADS 22 | # 23 | # Uncomment to use gettimeofday instead of clock_gettime. 24 | # CDEFS = CONVEY_USE_GETTIMEOFDAY 25 | # 26 | # Choose one of these as you need for threads or clock_gettime, and assign 27 | # to LDLIBS. 28 | # to LDLIBS. 29 | # 30 | # LDLIBS_Darwin = -lpthread 31 | # LDLIBS_Linux = -lpthread -lrt 32 | # LDLIBS_SunOS = -lpthread -lrt 33 | # LDLIBS_FreeBSD = -lpthread 34 | 35 | CC = cc 36 | CFLAGS = -I . 37 | RM = rm -f 38 | PROG = convey_test 39 | SRCS = convey_test.c convey.c 40 | OBJS = convey_test.o convey.o 41 | HDRS = convey.h 42 | 43 | run_tests: $(PROG) 44 | ./$(PROG) -v 45 | 46 | $(PROG): $(SRCS) $(HDRS) 47 | $(CC) $(CFLAGS) -o $@ $(SRCS) $(LDLIBS) 48 | 49 | clean: 50 | $(RM) $(PROG) $(OBJS) *.gcda *.gcno 51 | -------------------------------------------------------------------------------- /Makefile.win32: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Garrett D'Amore 3 | # 4 | # This software is supplied under the terms of the MIT License, a 5 | # copy of which should be located in the distribution where this 6 | # file was obtained (LICENSE.txt). A copy of the license may also be 7 | # found online at https://opensource.org/licenses/MIT. 8 | # 9 | 10 | # 11 | # Simple Makefile - for NMake on Win32 12 | # 13 | # The way the tests work is that you have to compile both your own .c 14 | # (containing your test code) and the test.c file that contains the 15 | # convey framework; you'll also need to have the header files in 16 | # the same directory, or located on the include search path. 17 | # 18 | all: run_tests 19 | 20 | CC = cl 21 | CFLAGS = -I . -D_WIN32 22 | RM = del 23 | PROG = convey_test.exe 24 | SRCS = convey_test.c convey.c 25 | OBJS = convey_test.obj convey.obj 26 | HDRS = convey.h 27 | 28 | run_tests: $(PROG) 29 | $(PROG) -v 30 | 31 | $(PROG): $(SRCS) $(HDRS) 32 | $(CC) $(CFLAGS) /Fe:$@ $(SRCS) 33 | 34 | clean: 35 | $(RM) $(PROG) $(OBJS) 36 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | C Convey Testing Framework 2 | ========================== 3 | 4 | image:https://img.shields.io/badge/license-MIT-blue.svg[MIT License] 5 | image:https://img.shields.io/travis/gdamore/c-convey/master.svg?label=linux[Linux Status,link="https://travis-ci.org/gdamore/c-convey"] 6 | image:https://img.shields.io/appveyor/ci/gdamore/c-convey/master.svg?label=windows[Windows Status,link="https://ci.appveyor.com/project/gdamore/c-convey"] 7 | image:https://codecov.io/gh/gdamore/c-convey/branch/master/graph/badge.svg?label=coverage[Coverage,link="https://codecov.io/gh/gdamore/c-convey"] 8 | image:https://api.codacy.com/project/badge/Grade/c8689e4c1fea4f359183fbafcc2c699d["Codacy code quality", link="https://www.codacy.com/app/gdamore/c-convey?utm_source=github.com&utm_medium=referral&utm_content=gdamore/c-convey&utm_campaign=Badge_Grade"] 9 | 10 | Inspired by the excellent 11 | https://github.com/smartystreets/goconvey/convey[GoConvey] framework, 12 | we have endeavored to create a similar framework for C programmers. 13 | This work was done in support of testing for a rewrite of 14 | https://github.com/nanomsg/nanomsg[nanomsg]. 15 | 16 | Easily Readable & Writeable Tests 17 | --------------------------------- 18 | 19 | image::screenshot_1.png[Convey Example] 20 | 21 | Each Convey() block is run on its own, with the entire of Convey's 22 | in which it is nested restarted from the beginning. It also has 23 | support for clean Reset(), allowing test pre-conditions to be reset 24 | between each set. 25 | 26 | Easily Readable Output 27 | ---------------------- 28 | 29 | image::screenshot_2.png[Terminal Output] 30 | 31 | The output is colored if yout terminal supports it, and tests can include 32 | extra debugging messages that will be inlined in the log in the appropriate 33 | calling order. (The test log is suppressed by default, and the above image 34 | shows a verbose, output, without the full debugging log.) The output above 35 | was generated by the demo.c program in this repository. 36 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Copyright 2016 Garrett D'Amore 3 | rem 4 | rem This software is supplied under the terms of the MIT License, a 5 | rem copy of which should be located in the distribution where this 6 | rem file was obtained (LICENSE.txt). A copy of the license may also be 7 | rem found online at https://opensource.org/licenses/MIT. 8 | 9 | rem This build script is for Win32 systems. You can also use the 10 | rem Makefile.win32. You need to set up your environment to include the 11 | rem PATH for cl.exe, as well as anything else it needs. Just run 12 | rem vsvars32.bat from Visual Studio first. 13 | 14 | set PROG=convey_test.exe 15 | set SRCS=convey_test.c convey.c 16 | set OBJS=convey_test.obj convey.obj 17 | set HDRS=convey.h 18 | 19 | set CC=cl 20 | set RM=del 21 | set CFLAGS=/I . 22 | set LDLIBS= 23 | 24 | if "%1" == "build" ( 25 | echo %CC% %CFLAGS% /Fe%PROG% %SRCS% %LDLIBS% 26 | %CC% %CFLAGS% /Fe%PROG% %SRCS% %LDLIBS% 27 | ) else if "%1" == "test" ( 28 | echo %PROG% %2 %3 %4 %5 %6 %7 %8 %9 29 | %PROG% %2 %3 %4 %5 %6 %7 %8 %9 30 | ) else if "%1" == "clean" ( 31 | echo %RM% %OBJS% %PROG% 32 | %RM% %OBJS% %PROG% 33 | ) else ( 34 | echo "Usage: %0 [args...]" 35 | ) 36 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2017 Garrett D'Amore 4 | # 5 | # This software is supplied under the terms of the MIT License, a 6 | # copy of which should be located in the distribution where this 7 | # file was obtained (LICENSE.txt). A copy of the license may also be 8 | # found online at https://opensource.org/licenses/MIT. 9 | # 10 | 11 | # 12 | # This build script is for building and running the tests. It knows 13 | # about Linux, MacOS X, and Solaris/illumos. Mostly this is for my 14 | # own testing, and for integrating with CI -- because writing portable 15 | # Makefiles is *hard*, and can't be bothered with CMake for this tiny 16 | # project. 17 | # 18 | # If cc is on your $PATH and a working compiler, the rest should just work. 19 | # 20 | 21 | PROG="convey_test" 22 | SRCS="convey_test.c convey.c" 23 | HDRS="convey.h" 24 | OBJS="convey_test.o convey.o" 25 | 26 | CC=${CC:-cc} 27 | RM="${RM:-rm -f}" 28 | CFLAGS="${CFLAGS:- -I .} ${EXTRA_CFLAGS}" 29 | 30 | case ${UNAME:-`uname`} in 31 | Linux) 32 | LDLIBS="${LDLIBS:- -lpthread -lrt }" 33 | ;; 34 | SunOS) 35 | LDLIBS="${LDLIBS:- -lpthread -lrt }" 36 | ;; 37 | Darwin) 38 | LDLIBS="${LDLIBS:- -lpthread }" 39 | ;; 40 | *) 41 | echo "Unknown OS, using safe, unthreaded mode." 42 | # LDLIBS=${LDLIBS:-} 43 | CFLAGS="${CFLAGS} -DCONVEY_NO_THREAD -DCONVEY_USE_GETTIMEOFDAY" 44 | ;; 45 | esac 46 | 47 | build() { 48 | ${CC} ${CFLAGS} -o ${PROG} ${SRCS} ${LDLIBS} 49 | } 50 | 51 | test() { 52 | if [ ! -f ${PROG} ] 53 | then 54 | build 55 | fi 56 | ./${PROG} $* 57 | } 58 | 59 | coverage() { 60 | ${CC} -g -O0 -fprofile-arcs -ftest-coverage ${CFLAGS} -o ${PROG} ${SRCS} ${LDLIBS} 61 | ./${PROG} $* || exit 1 62 | ./${PROG} -v || exit 1 63 | ./${PROG} -d || exit 1 64 | ./${PROG} -v -d || exit 1 65 | ./${PROG} -v || exit 1 66 | ./${PROG} -v < /dev/null || exit 1 67 | ./${PROG} -v -p TEST_FAIL || exit 1 68 | ./${PROG} -p ENVTEST=ON -p ANOTHERNAME -p AGAIN=YES barearg || exit 1 69 | env TERM=dumb ./${PROG} -v || exit 1 70 | } 71 | 72 | clean() { 73 | ${RM} ${OBJS} ${PROG} 74 | } 75 | 76 | case "$1" in 77 | build|test|coverage|clean) 78 | fn="$1" 79 | shift 80 | ${fn} "$*" 81 | ;; 82 | 83 | *) 84 | echo "Usage: $0 [args...]\n" 85 | exit 2 86 | ;; 87 | esac 88 | -------------------------------------------------------------------------------- /convey.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Garrett D'Amore 3 | * 4 | * This software is supplied under the terms of the MIT License, a 5 | * copy of which should be located in the distribution where this 6 | * file was obtained (LICENSE.txt). A copy of the license may also be 7 | * found online at https://opensource.org/licenses/MIT. 8 | */ 9 | 10 | /* 11 | * This contains some of the guts of the testing framework. It is in a single 12 | * file in order to simplify use and minimize external dependencies. 13 | * 14 | * If you use this with threads, you need to either have pthreads (and link 15 | * your test program against the threading library), or you need Windows. 16 | * Support for C11 threading is not implemented yet. 17 | * 18 | * For timing, this code needs a decent timer. It will use clock_gettime 19 | * if it appears to be present, or the Win32 QueryPerformanceCounter, or 20 | * gettimeofday() if neither of those are available. 21 | * 22 | * This code is unlikely to function at all on anything that isn't a UNIX 23 | * or Windows system. As we think its unlikely that you'd want to use this 24 | * to run testing inside an embedded device or something, we think this is a 25 | * reasonable limitation. 26 | * 27 | * Note that we expect that on Windows, you have a reasonably current 28 | * version of MSVC. (Specifically we need a few C99-isms that Microsoft 29 | * only added late -- like in 2010. Specifically uint32_t and uint64_t). 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #ifdef _WIN32 41 | #include 42 | 43 | #else 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #ifndef CONVEY_NO_THREADS 52 | #include 53 | #endif 54 | 55 | #endif 56 | 57 | #include "convey.h" 58 | 59 | /* 60 | * About symbol naming. We use Go-like conventions to help set expectations, 61 | * even though we cannot necessarily count on the linker to prevent 62 | * access. We have to accept that we may be inlined directly into the 63 | * user's program, so we try not to pollute their namespace. (Convenience 64 | * pollution may be enabled in convey.h.) 65 | * 66 | * Symbols exposed to users directly are named ConveyXXX using CamelCase 67 | * (just like Go). 68 | * 69 | * Symbols used internally, but which must be exposed for external linkage 70 | * will be named using conveyXXX (camelCase with the leading "c" lower.) 71 | * 72 | * Symbols used internally, and kept entirely within the the .c file, are 73 | * named convey_xxx (using underscores). 74 | * 75 | * When symbols can reasonably be expected not to collide and are local to 76 | * a scope not expressed to user code, these rules are relaxed. 77 | */ 78 | 79 | static const char *convey_sym_pass = "."; 80 | static const char *convey_sym_skip = "?"; 81 | static const char *convey_sym_fail = "X"; 82 | static const char *convey_sym_fatal = "!"; 83 | static const char *convey_nocolor = ""; 84 | static const char *convey_green = ""; 85 | static const char *convey_red = ""; 86 | static const char *convey_yellow = ""; 87 | 88 | static int convey_debug = 0; 89 | static int convey_verbose = 0; 90 | static int convey_nassert = 0; 91 | static int convey_nskip = 0; 92 | static const char *convey_assert_color = ""; 93 | 94 | #if defined(_WIN32) 95 | static WORD convey_defattr; 96 | static HANDLE convey_console; 97 | #endif 98 | 99 | #define CONVEY_EXIT_OK 0 100 | #define CONVEY_EXIT_USAGE 1 101 | #define CONVEY_EXIT_FAIL 2 102 | #define CONVEY_EXIT_FATAL 3 103 | #define CONVEY_EXIT_NOMEM 4 104 | 105 | struct convey_timer { 106 | uint64_t timer_base; 107 | uint64_t timer_count; 108 | uint64_t timer_rate; 109 | int timer_running; 110 | }; 111 | 112 | struct convey_log { 113 | char * log_buf; 114 | size_t log_size; 115 | size_t log_length; 116 | }; 117 | 118 | struct convey_ctx { 119 | char ctx_name[256]; 120 | struct convey_ctx * ctx_parent; 121 | struct convey_ctx * ctx_root; /* the root node on the list */ 122 | struct convey_ctx * ctx_next; /* root list only, cleanup */ 123 | int ctx_level; 124 | int ctx_done; 125 | int ctx_started; 126 | jmp_buf * ctx_jmp; 127 | int ctx_fatal; 128 | int ctx_fail; 129 | int ctx_skip; 130 | int ctx_printed; 131 | struct convey_timer ctx_timer; 132 | struct convey_log * ctx_errlog; 133 | struct convey_log * ctx_faillog; 134 | struct convey_log * ctx_dbglog; 135 | }; 136 | 137 | static void convey_print_result(struct convey_ctx *); 138 | static void convey_init_timer(struct convey_timer *); 139 | static void convey_start_timer(struct convey_timer *); 140 | static void convey_stop_timer(struct convey_timer *); 141 | static void convey_read_timer(struct convey_timer *, int *, int *); 142 | static void convey_init_term(void); 143 | static int convey_tls_init(void); 144 | static void *convey_tls_get(void); 145 | static int convey_tls_set(void *); 146 | static struct convey_ctx *convey_get_ctx(void); 147 | static void convey_vlogf(struct convey_log *, const char *, va_list, int); 148 | static void convey_logf(struct convey_log *, const char *, ...); 149 | static void convey_log_emit(struct convey_log *, const char *, const char *); 150 | static void convey_log_free(struct convey_log *); 151 | static struct convey_log *convey_log_alloc(void); 152 | static char * convey_nextline(char **); 153 | static void convey_emit_color(const char *); 154 | 155 | /* 156 | * convey_emit_color just changes the output text to the color 157 | * requested. It is Windows console aware. 158 | */ 159 | static void 160 | convey_emit_color(const char *color) 161 | { 162 | #if defined(_WIN32) 163 | 164 | if (convey_console != INVALID_HANDLE_VALUE) { 165 | WORD attr; 166 | 167 | attr = convey_defattr & 168 | ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | 169 | FOREGROUND_INTENSITY); 170 | 171 | if (color == convey_nocolor) { 172 | attr = convey_defattr; 173 | } else if (color == convey_yellow) { 174 | attr |= FOREGROUND_GREEN | FOREGROUND_RED | 175 | FOREGROUND_INTENSITY; 176 | } else if (color == convey_green) { 177 | attr |= FOREGROUND_GREEN | FOREGROUND_INTENSITY; 178 | } else if (color == convey_red) { 179 | attr |= FOREGROUND_RED | FOREGROUND_INTENSITY; 180 | } else { 181 | return; 182 | } 183 | (void) fflush(stdout); 184 | SetConsoleTextAttribute(convey_console, attr); 185 | } else { 186 | (void) fputs(color, stdout); 187 | } 188 | #else 189 | (void) fputs(color, stdout); 190 | #endif 191 | } 192 | 193 | /* 194 | * convey_print_result prints the test results. It prints more information 195 | * in convey_verbose mode. Note that its possible for assertion checks done at 196 | * a given block to be recorded in a deeper block, since we can't easily 197 | * go back up to the old line and print it. 198 | * 199 | * We also leverage this point to detect completion of a root context, and 200 | * deallocate the child contexts. The root context should never be reentered 201 | * here. 202 | */ 203 | static void 204 | convey_print_result(struct convey_ctx *t) 205 | { 206 | int secs, usecs; 207 | 208 | if (t->ctx_root == t) { 209 | convey_stop_timer(&t->ctx_timer); /* This is idempotent */ 210 | 211 | convey_read_timer(&t->ctx_timer, &secs, &usecs); 212 | 213 | (void) convey_logf(t->ctx_dbglog, "Test %s: %s (%d.%02ds)\n", 214 | t->ctx_fatal ? "FATAL" 215 | : t->ctx_fail 216 | ? "FAIL" 217 | : t->ctx_skip ? "PASS (with SKIPs)" : "PASS", 218 | t->ctx_name, secs, usecs / 10000); 219 | 220 | if (convey_verbose) { 221 | (void) puts(""); 222 | } 223 | convey_log_emit(t->ctx_errlog, "Errors:", convey_red); 224 | convey_log_emit(t->ctx_faillog, "Failures:", convey_yellow); 225 | if (convey_debug) { 226 | convey_log_emit(t->ctx_dbglog, "Log:", convey_nocolor); 227 | } 228 | if (convey_verbose) { 229 | (void) puts(""); 230 | (void) puts(""); 231 | convey_emit_color(convey_assert_color); 232 | (void) printf( 233 | "%d assertions thus far", convey_nassert); 234 | convey_emit_color(convey_nocolor); 235 | 236 | if (convey_nskip) { 237 | (void) fputs(" ", stdout); 238 | convey_emit_color(convey_yellow); 239 | (void) fputs( 240 | "(one or more sections skipped)", stdout); 241 | convey_emit_color(convey_nocolor); 242 | } 243 | (void) printf("\n\n--- %s: %s (%d.%02ds)\n", 244 | t->ctx_fatal ? "FATAL" 245 | : t->ctx_fail ? "FAIL" : "PASS", 246 | t->ctx_name, secs, usecs / 10000); 247 | } 248 | 249 | /* Remove the context, because we cannot reenter here */ 250 | convey_tls_set(NULL); 251 | 252 | while (t != NULL) { 253 | struct convey_ctx *freeit = t; 254 | if (t->ctx_root == t) { 255 | convey_log_free(t->ctx_dbglog); 256 | convey_log_free(t->ctx_faillog); 257 | convey_log_free(t->ctx_errlog); 258 | } 259 | t = t->ctx_next; 260 | memset(freeit, 0, sizeof(*freeit)); 261 | free(freeit); 262 | } 263 | } 264 | } 265 | 266 | /* 267 | * conveyStart is called when the context starts, before any call to 268 | * setjmp is made. If the context isn't initialized already, that is 269 | * done. Note that this code gets called multiple times when the 270 | * context is reentered, which is why the context used must be statically 271 | * allocated -- a record that it has already done is checked. If 272 | * the return value is zero, then this block has already been executed, 273 | * and it should be skipped. Otherwise, it needs to be done. 274 | */ 275 | int 276 | conveyStart(conveyScope *scope, const char *name) 277 | { 278 | struct convey_ctx *t, *parent; 279 | 280 | parent = convey_get_ctx(); 281 | 282 | if ((t = scope->cs_data) != NULL) { 283 | if (t->ctx_done) { 284 | convey_print_result(t); 285 | return (1); /* all done, skip */ 286 | } 287 | return (0); /* continue onward */ 288 | } 289 | scope->cs_data = (t = calloc(1, sizeof(struct convey_ctx))); 290 | if (t == NULL) { 291 | goto allocfail; 292 | } 293 | t->ctx_jmp = &scope->cs_jmp; 294 | 295 | (void) snprintf(t->ctx_name, sizeof(t->ctx_name) - 1, "%s", name); 296 | 297 | if (parent != NULL) { 298 | t->ctx_parent = parent; 299 | t->ctx_root = t->ctx_parent->ctx_root; 300 | t->ctx_level = t->ctx_parent->ctx_level + 1; 301 | /* unified logging against the root context */ 302 | t->ctx_dbglog = t->ctx_root->ctx_dbglog; 303 | t->ctx_faillog = t->ctx_root->ctx_faillog; 304 | t->ctx_errlog = t->ctx_root->ctx_errlog; 305 | t->ctx_next = t->ctx_root->ctx_next; 306 | t->ctx_root->ctx_next = t; 307 | } else { 308 | t->ctx_parent = t; 309 | t->ctx_root = t; 310 | if (((t->ctx_errlog = convey_log_alloc()) == NULL) || 311 | ((t->ctx_faillog = convey_log_alloc()) == NULL) || 312 | ((t->ctx_dbglog = convey_log_alloc()) == NULL)) { 313 | goto allocfail; 314 | } 315 | convey_logf(t->ctx_dbglog, "Test Started: %s\n", t->ctx_name); 316 | } 317 | return (0); 318 | 319 | allocfail: 320 | if (t != NULL) { 321 | convey_log_free(t->ctx_errlog); 322 | convey_log_free(t->ctx_dbglog); 323 | convey_log_free(t->ctx_faillog); 324 | free(t); 325 | scope->cs_data = NULL; 326 | } 327 | if (parent != NULL) { 328 | ConveyError("Unable to allocate context"); 329 | } 330 | return (1); 331 | } 332 | 333 | /* 334 | * conveyLoop is called right after setjmp. If unwind is true it indicates 335 | * that setjmp returned true, and we are unwinding the stack. In that case 336 | * we perform a local cleanup and keep popping back up the stack. We 337 | * always come through this, even if the test finishes successfully, so 338 | * that we can do this stack unwind. If we are unwinding, and we are 339 | * at the root context, then we pritn the results and return non-zero 340 | * so that our caller knows to stop further processing. 341 | */ 342 | int 343 | conveyLoop(conveyScope *scope, int unwind) 344 | { 345 | struct convey_ctx *t; 346 | int i; 347 | 348 | if ((t = scope->cs_data) == NULL) { 349 | return (1); 350 | } 351 | if (unwind) { 352 | if ((t->ctx_parent != t) && (t->ctx_parent != NULL)) { 353 | longjmp(*t->ctx_parent->ctx_jmp, 1); 354 | } 355 | if (t->ctx_done) { 356 | convey_print_result(t); 357 | return (1); 358 | } 359 | } 360 | if (!t->ctx_started) { 361 | t->ctx_started = 1; 362 | 363 | if (convey_verbose) { 364 | if (t->ctx_root == t) { 365 | (void) printf("=== RUN: %s\n", t->ctx_name); 366 | } else { 367 | (void) puts(""); 368 | for (i = 0; i < t->ctx_level; i++) { 369 | (void) fputs(" ", stdout); 370 | } 371 | (void) printf("%s ", t->ctx_name); 372 | (void) fflush(stdout); 373 | } 374 | } 375 | 376 | convey_init_timer(&t->ctx_timer); 377 | convey_start_timer(&t->ctx_timer); 378 | } 379 | /* Reset TC for the following code. */ 380 | convey_tls_set(t); 381 | return (0); 382 | } 383 | 384 | void 385 | conveyFinish(conveyScope *scope, int *rvp) 386 | { 387 | struct convey_ctx *t; 388 | 389 | if ((t = scope->cs_data) == NULL) { 390 | /* allocation failure */ 391 | *rvp = CONVEY_EXIT_NOMEM; 392 | return; 393 | } 394 | t->ctx_done = 1; 395 | if (rvp != NULL) { 396 | /* exit code 1 is reserved for usage errors */ 397 | if (t->ctx_fatal) { 398 | *rvp = CONVEY_EXIT_FATAL; 399 | } else if (t->ctx_fail) { 400 | *rvp = CONVEY_EXIT_FAIL; 401 | } else { 402 | *rvp = CONVEY_EXIT_OK; 403 | } 404 | } 405 | longjmp(*t->ctx_jmp, 1); 406 | } 407 | 408 | void 409 | conveySkip(const char *file, int line, const char *fmt, ...) 410 | { 411 | va_list ap; 412 | struct convey_ctx *t = convey_get_ctx(); 413 | struct convey_log *dlog = t->ctx_dbglog; 414 | 415 | if (convey_verbose) { 416 | convey_emit_color(convey_yellow); 417 | (void) fputs(convey_sym_skip, stdout); 418 | convey_emit_color(convey_nocolor); 419 | } 420 | convey_logf(dlog, "* %s (%s:%d) (Skip): ", t->ctx_name, file, line); 421 | va_start(ap, fmt); 422 | convey_vlogf(dlog, fmt, ap, 1); 423 | va_end(ap); 424 | t->ctx_done = 1; /* This forces an end */ 425 | convey_nskip++; 426 | longjmp(*t->ctx_jmp, 1); 427 | } 428 | 429 | void 430 | conveyAssertFail(const char *cond, const char *file, int line) 431 | { 432 | struct convey_ctx *t = convey_get_ctx(); 433 | 434 | convey_nassert++; 435 | if (convey_verbose) { 436 | convey_emit_color(convey_yellow); 437 | (void) fputs(convey_sym_fail, stdout); 438 | convey_emit_color(convey_nocolor); 439 | } 440 | if (t->ctx_root != t) { 441 | t->ctx_root->ctx_fail++; 442 | } 443 | convey_assert_color = convey_yellow; 444 | t->ctx_fail++; 445 | t->ctx_done = 1; /* This forces an end */ 446 | convey_logf(t->ctx_faillog, "* Assertion Failed (%s)\n", t->ctx_name); 447 | convey_logf(t->ctx_faillog, "File: %s\n", file); 448 | convey_logf(t->ctx_faillog, "Line: %d\n", line); 449 | convey_logf(t->ctx_faillog, "Test: %s\n\n", cond); 450 | convey_logf(t->ctx_dbglog, "* %s (%s:%d) (FAILED): %s\n", t->ctx_name, 451 | file, line, cond); 452 | longjmp(*t->ctx_jmp, 1); 453 | } 454 | 455 | void 456 | conveyAssertPass(const char *cond, const char *file, int line) 457 | { 458 | struct convey_ctx *t = convey_get_ctx(); 459 | 460 | convey_nassert++; 461 | if (convey_verbose) { 462 | convey_emit_color(convey_green); 463 | (void) fputs(convey_sym_pass, stdout); 464 | convey_emit_color(convey_nocolor); 465 | } 466 | convey_logf(t->ctx_dbglog, "* %s (%s:%d) (Passed): %s\n", t->ctx_name, 467 | file, line, cond); 468 | } 469 | 470 | void 471 | conveyAssertSkip(const char *cond, const char *file, int line) 472 | { 473 | struct convey_ctx *t = convey_get_ctx(); 474 | 475 | convey_nskip++; 476 | if (convey_verbose) { 477 | convey_emit_color(convey_yellow); 478 | (void) fputs(convey_sym_pass, stdout); 479 | convey_emit_color(convey_nocolor); 480 | } 481 | convey_logf(t->ctx_dbglog, "* %s (%s:%d) (Skip): %s\n", t->ctx_name, 482 | file, line, cond); 483 | } 484 | 485 | /* 486 | * Performance counters. Really we just want to start and stop timers, to 487 | * measure elapsed time in usec. 488 | */ 489 | static void 490 | convey_init_timer(struct convey_timer *pc) 491 | { 492 | memset(pc, 0, sizeof(*pc)); 493 | } 494 | 495 | static void 496 | convey_start_timer(struct convey_timer *pc) 497 | { 498 | if (pc->timer_running) { 499 | return; 500 | } 501 | #if defined(_WIN32) 502 | LARGE_INTEGER pcnt, pfreq; 503 | QueryPerformanceCounter(&pcnt); 504 | QueryPerformanceFrequency(&pfreq); 505 | pc->timer_base = pcnt.QuadPart; 506 | pc->timer_rate = pfreq.QuadPart; 507 | #elif defined(CLOCK_MONOTONIC) && !defined(CONVEY_USE_GETTIMEOFDAY) 508 | uint64_t usecs; 509 | struct timespec ts; 510 | 511 | clock_gettime(CLOCK_MONOTONIC, &ts); 512 | pc->timer_base = ts.tv_sec * 1000000000; 513 | pc->timer_base += ts.tv_nsec; 514 | pc->timer_rate = 1000000000; 515 | #else 516 | struct timeval tv; 517 | 518 | gettimeofday(&tv, NULL); 519 | pc->timer_base = tv.tv_sec * 1000000; 520 | pc->timer_base += tv.tv_usec; 521 | pc->timer_rate = 1000000; 522 | #endif 523 | pc->timer_running = 1; 524 | } 525 | 526 | static void 527 | convey_stop_timer(struct convey_timer *pc) 528 | { 529 | if (!pc->timer_running) { 530 | return; 531 | } 532 | do { 533 | #if defined(_WIN32) 534 | LARGE_INTEGER pcnt; 535 | QueryPerformanceCounter(&pcnt); 536 | pc->timer_count += (pcnt.QuadPart - pc->timer_base); 537 | #elif defined(CLOCK_MONOTONIC) && !defined(CONVEY_USE_GETTIMEOFDAY) 538 | uint64_t ns; 539 | struct timespec ts; 540 | 541 | clock_gettime(CLOCK_MONOTONIC, &ts); 542 | ns = (ts.tv_sec * 1000000000); 543 | ns += ts.tv_nsec; 544 | pc->timer_count += (ns - pc->timer_base); 545 | #else 546 | uint64_t us; 547 | struct timeval tv; 548 | 549 | gettimeofday(&tv, NULL); 550 | us = (tv.tv_sec * 1000000); 551 | us += tv.tv_usec; 552 | pc->timer_count += (us - pc->timer_base); 553 | #endif 554 | } while (0); 555 | } 556 | 557 | static void 558 | convey_read_timer(struct convey_timer *pc, int *secp, int *usecp) 559 | { 560 | uint64_t delta, rate, sec, usec; 561 | 562 | delta = pc->timer_count; 563 | rate = pc->timer_rate; 564 | 565 | sec = delta / rate; 566 | delta -= (sec * rate); 567 | 568 | /* 569 | * done this way we avoid dividing rate by 1M -- and the above 570 | * ensures we don't wrap. 571 | */ 572 | usec = (delta * 1000000) / rate; 573 | 574 | if (secp) { 575 | *secp = (int) sec; 576 | } 577 | if (usecp) { 578 | *usecp = (int) usec; 579 | } 580 | } 581 | 582 | /* 583 | * Thread-specific data. Pthreads uses one way, Win32 another. If you 584 | * lack threads, just #define CONVEY_NO_THREADS. C11 thread support is 585 | * pending. 586 | */ 587 | 588 | #ifdef CONVEY_NO_THREADS 589 | static void *convey_tls_key; 590 | 591 | static int 592 | convey_tls_init(void) 593 | { 594 | return (0); 595 | } 596 | 597 | static int 598 | convey_tls_set(void *v) 599 | { 600 | convey_tls_key = v; 601 | return (0); 602 | } 603 | 604 | static void * 605 | convey_tls_get(void) 606 | { 607 | return (convey_tls_key); 608 | } 609 | 610 | #elif defined(_WIN32) 611 | 612 | static DWORD convey_tls_key; 613 | 614 | static int 615 | convey_tls_init(void) 616 | { 617 | if ((convey_tls_key = TlsAlloc()) == TLS_OUT_OF_INDEXES) { 618 | return (-1); 619 | } 620 | return (0); 621 | } 622 | 623 | static int 624 | convey_tls_set(void *v) 625 | { 626 | if (!TlsSetValue(convey_tls_key, v)) { 627 | return (-1); 628 | } 629 | return (0); 630 | } 631 | 632 | static void * 633 | convey_tls_get(void) 634 | { 635 | return ((void *) TlsGetValue(convey_tls_key)); 636 | } 637 | 638 | #else 639 | 640 | pthread_key_t convey_tls_key; 641 | 642 | static int 643 | convey_tls_init(void) 644 | { 645 | if (pthread_key_create(&convey_tls_key, NULL) != 0) { 646 | return (-1); 647 | } 648 | return (0); 649 | } 650 | 651 | static int 652 | convey_tls_set(void *v) 653 | { 654 | if (pthread_setspecific(convey_tls_key, v) != 0) { 655 | return (-1); 656 | } 657 | return (0); 658 | } 659 | 660 | static void * 661 | convey_tls_get(void) 662 | { 663 | return (pthread_getspecific(convey_tls_key)); 664 | } 665 | 666 | #endif 667 | 668 | static struct convey_ctx * 669 | convey_get_ctx(void) 670 | { 671 | return (convey_tls_get()); 672 | } 673 | 674 | /* 675 | * Log stuff. 676 | */ 677 | static void 678 | convey_vlogf(struct convey_log *log, const char *fmt, va_list va, int addnl) 679 | { 680 | /* Grow the log buffer if we need to */ 681 | while ((log->log_size - log->log_length) < 256) { 682 | size_t newsz = log->log_size + 2000; 683 | char * ptr = malloc(newsz); 684 | if (ptr == NULL) { 685 | return; 686 | } 687 | memcpy(ptr, log->log_buf, log->log_length); 688 | memset(ptr + log->log_length, 0, newsz - log->log_length); 689 | free(log->log_buf); 690 | log->log_buf = ptr; 691 | log->log_size = newsz; 692 | } 693 | 694 | /* 2 allows space for NULL, and newline */ 695 | (void) vsnprintf(log->log_buf + log->log_length, 696 | log->log_size - (log->log_length + 2), fmt, va); 697 | log->log_length += strlen(log->log_buf + log->log_length); 698 | if (addnl && (log->log_buf[log->log_length - 1] != '\n')) { 699 | log->log_buf[log->log_length++] = '\n'; 700 | } 701 | } 702 | 703 | static void 704 | convey_logf(struct convey_log *log, const char *fmt, ...) 705 | { 706 | va_list va; 707 | 708 | va_start(va, fmt); 709 | convey_vlogf(log, fmt, va, 0); 710 | va_end(va); 711 | } 712 | 713 | static void 714 | convey_log_emit(struct convey_log *log, const char *header, const char *color) 715 | { 716 | char *s; 717 | char *last = log->log_buf; 718 | 719 | if (log->log_length == 0) { 720 | return; 721 | } 722 | 723 | (void) fputs("\n\n", stdout); 724 | convey_emit_color(color); 725 | (void) fputs(header, stdout); 726 | convey_emit_color(convey_nocolor); 727 | (void) fputs("\n\n", stdout); 728 | while ((s = convey_nextline(&last)) != NULL) { 729 | (void) fputs(" ", stdout); 730 | convey_emit_color(color); 731 | (void) fputs(s, stdout); 732 | convey_emit_color(convey_nocolor); 733 | (void) fputs("\n", stdout); 734 | } 735 | } 736 | 737 | static void 738 | convey_log_free(struct convey_log *log) 739 | { 740 | if (log != NULL) { 741 | if (log->log_size != 0) { 742 | free(log->log_buf); 743 | } 744 | free(log); 745 | } 746 | } 747 | 748 | static struct convey_log * 749 | convey_log_alloc(void) 750 | { 751 | return (calloc(1, sizeof(struct convey_log))); 752 | } 753 | 754 | /* 755 | * ConveyInit initializes some common global stuff. Call it from main(), 756 | * if you don't use the framework provided main. 757 | */ 758 | int 759 | ConveyInit(void) 760 | { 761 | static int inited; 762 | 763 | if (!inited) { 764 | if (convey_tls_init() != 0) { 765 | return (-1); 766 | } 767 | convey_init_term(); 768 | inited = 1; 769 | } 770 | return (0); 771 | } 772 | 773 | void 774 | ConveySetVerbose(void) 775 | { 776 | convey_verbose = 1; 777 | } 778 | 779 | void 780 | conveyFail(const char *file, int line, const char *fmt, ...) 781 | { 782 | struct convey_ctx *t = convey_get_ctx(); 783 | struct convey_log *flog = t->ctx_faillog; 784 | struct convey_log *dlog = t->ctx_dbglog; 785 | va_list ap; 786 | 787 | convey_logf(dlog, "* %s (%s:%d) (Failed): ", t->ctx_name, file, line); 788 | va_start(ap, fmt); 789 | convey_vlogf(dlog, fmt, ap, 1); 790 | va_end(ap); 791 | 792 | convey_logf(flog, "* %s\n", t->ctx_root->ctx_name); 793 | convey_logf(flog, "File: %s\n", file); 794 | convey_logf(flog, "Line: %d\n", line); 795 | convey_logf(flog, "Reason: "); 796 | va_start(ap, fmt); 797 | convey_vlogf(flog, fmt, ap, 1); 798 | va_end(ap); 799 | 800 | if (t->ctx_root != t) { 801 | t->ctx_root->ctx_fail++; 802 | } 803 | convey_assert_color = convey_yellow; 804 | t->ctx_fail++; 805 | t->ctx_done = 1; /* This forces an end */ 806 | longjmp(*t->ctx_jmp, 1); 807 | } 808 | 809 | void 810 | conveyError(const char *file, int line, const char *fmt, ...) 811 | { 812 | struct convey_ctx *t = convey_get_ctx(); 813 | struct convey_log *flog = t->ctx_errlog; 814 | struct convey_log *dlog = t->ctx_dbglog; 815 | va_list ap; 816 | 817 | convey_logf(dlog, "* %s (%s:%d) (Error): ", t->ctx_name, file, line); 818 | va_start(ap, fmt); 819 | convey_vlogf(dlog, fmt, ap, 1); 820 | va_end(ap); 821 | 822 | convey_logf(flog, "* %s\n", t->ctx_root->ctx_name); 823 | convey_logf(flog, "File: %s\n", file); 824 | convey_logf(flog, "Line: %d\n", line); 825 | convey_logf(flog, "Reason: "); 826 | va_start(ap, fmt); 827 | convey_vlogf(flog, fmt, ap, 1); 828 | va_end(ap); 829 | 830 | if (t->ctx_root != t) { 831 | t->ctx_root->ctx_fail++; 832 | } 833 | convey_assert_color = convey_red; 834 | t->ctx_fail++; 835 | t->ctx_done = 1; /* This forces an end */ 836 | longjmp(*t->ctx_jmp, 1); 837 | } 838 | 839 | void 840 | conveyPrintf(const char *file, int line, const char *fmt, ...) 841 | { 842 | va_list ap; 843 | struct convey_ctx *t = convey_get_ctx(); 844 | struct convey_log *dlog = t->ctx_dbglog; 845 | 846 | convey_logf(dlog, "* %s (%s:%d) (Debug): ", t->ctx_name, file, line); 847 | va_start(ap, fmt); 848 | convey_vlogf(dlog, fmt, ap, 1); 849 | va_end(ap); 850 | } 851 | 852 | extern int conveyMainImpl(void); 853 | 854 | static void 855 | convey_init_term(void) 856 | { 857 | const char *term; 858 | 859 | #ifndef _WIN32 860 | /* Windows console doesn't do Unicode (consistently). */ 861 | const char *codeset; 862 | 863 | (void) setlocale(LC_ALL, ""); 864 | codeset = nl_langinfo(CODESET); 865 | if ((codeset != NULL) && (strcmp(codeset, "UTF-8") == 0)) { 866 | convey_sym_pass = "✔"; 867 | convey_sym_fail = "✘"; 868 | convey_sym_fatal = "🔥"; 869 | convey_sym_skip = "⚠"; 870 | } 871 | term = getenv("TERM"); 872 | if (!isatty(fileno(stdin))) { 873 | term = NULL; 874 | } 875 | 876 | #else 877 | CONSOLE_SCREEN_BUFFER_INFO info; 878 | 879 | convey_console = GetStdHandle(STD_OUTPUT_HANDLE); 880 | if (!GetConsoleScreenBufferInfo(convey_console, &info)) { 881 | convey_console = INVALID_HANDLE_VALUE; 882 | } else { 883 | convey_defattr = info.wAttributes; 884 | // Values probably don't matter, just need to be 885 | // different! 886 | convey_nocolor = "\033[0m"; 887 | convey_green = "\033[32m"; 888 | convey_yellow = "\033[33m"; 889 | convey_red = "\033[31m"; 890 | } 891 | term = getenv("TERM"); 892 | #endif 893 | 894 | if (term != NULL) { 895 | if ((strstr(term, "xterm") != NULL) || 896 | (strstr(term, "ansi") != NULL) || 897 | (strstr(term, "color") != NULL)) { 898 | convey_nocolor = "\033[0m"; 899 | convey_green = "\033[32m"; 900 | convey_yellow = "\033[33m"; 901 | convey_red = "\033[31m"; 902 | } 903 | } 904 | convey_assert_color = convey_green; 905 | } 906 | 907 | /* 908 | * This function exists because strtok isn't safe, and strtok_r and 909 | * strsep are not universally available. Its like strsep, but only does 910 | * newlines. Could be implemented using strpbrk, but this is probably 911 | * faster since we are only looking for a single character. 912 | */ 913 | static char * 914 | convey_nextline(char **next) 915 | { 916 | char *line = *next; 917 | char *nl; 918 | char c; 919 | 920 | if (line == NULL) { 921 | return (NULL); 922 | } 923 | for (nl = line; (c = (*nl)) != '\0'; nl++) { 924 | if (c == '\n') { 925 | *nl = '\0'; 926 | *next = nl + 1; 927 | return (line); 928 | } 929 | } 930 | 931 | /* 932 | * If the last character in the file is a newline, treat it as 933 | * the end. (This will appear as a blank last line.) 934 | */ 935 | if (*line == '\0') { 936 | line = NULL; 937 | } 938 | *next = NULL; 939 | return (line); 940 | } 941 | 942 | static struct convey_env { 943 | struct convey_env *next; 944 | const char * name; 945 | char * value; 946 | } * convey_environment; 947 | 948 | static struct convey_env * 949 | conveyFindEnv(const char *name) 950 | { 951 | struct convey_env *ev; 952 | for (ev = convey_environment; ev != NULL; ev = ev->next) { 953 | if (strcmp(name, ev->name) == 0) { 954 | return (ev); 955 | } 956 | } 957 | return (NULL); 958 | } 959 | 960 | char * 961 | conveyGetEnv(const char *name) 962 | { 963 | struct convey_env *ev; 964 | 965 | if ((ev = conveyFindEnv(name)) != NULL) { 966 | return (ev->value); 967 | } 968 | return (getenv(name)); 969 | } 970 | 971 | int 972 | conveyPutEnv(const char *name, char *value) 973 | { 974 | struct convey_env *env; 975 | 976 | if ((env = conveyFindEnv(name)) == NULL) { 977 | env = malloc(sizeof(*env)); 978 | if (env == NULL) { 979 | return (-1); 980 | } 981 | env->next = convey_environment; 982 | convey_environment = env; 983 | } 984 | env->name = name; 985 | env->value = value; 986 | return (0); 987 | } 988 | 989 | int 990 | conveyMain(int argc, char **argv) 991 | { 992 | int i; 993 | const char * status; 994 | const char * prog = ""; 995 | struct convey_timer pc; 996 | int secs, usecs; 997 | struct convey_env * env; 998 | 999 | if ((argc > 0) && (argv[0] != NULL)) { 1000 | prog = argv[0]; 1001 | } 1002 | 1003 | /* 1004 | * Poor man's getopt. Very poor. We should add a way for tests 1005 | * to retrieve additional test specific options. 1006 | */ 1007 | for (i = 1; i < argc; i++) { 1008 | if (argv[i][0] != '-') { 1009 | break; 1010 | } 1011 | if (strcmp(argv[i], "-v") == 0) { 1012 | ConveySetVerbose(); 1013 | continue; 1014 | } 1015 | if (strcmp(argv[i], "-d") == 0) { 1016 | convey_debug++; 1017 | continue; 1018 | } 1019 | if ((strcmp(argv[i], "-p") == 0) && ((i + 1) < argc)) { 1020 | char *delim; 1021 | if ((delim = strchr(argv[i + 1], '=')) != NULL) { 1022 | *delim = '\0'; 1023 | conveyPutEnv(argv[i + 1], delim + 1); 1024 | } else { 1025 | conveyPutEnv(argv[i + 1], ""); 1026 | } 1027 | i++; 1028 | continue; 1029 | } 1030 | } 1031 | if (ConveyInit() != 0) { 1032 | (void) fputs("Cannot initialize test framework\n", stderr); 1033 | exit(CONVEY_EXIT_NOMEM); 1034 | } 1035 | 1036 | convey_init_timer(&pc); 1037 | convey_start_timer(&pc); 1038 | i = conveyMainImpl(); 1039 | convey_stop_timer(&pc); 1040 | 1041 | switch (i) { 1042 | case CONVEY_EXIT_NOMEM: 1043 | (void) fputs("Cannot initialize root test context\n", stderr); 1044 | exit(CONVEY_EXIT_NOMEM); 1045 | case CONVEY_EXIT_OK: 1046 | if (convey_verbose) { 1047 | (void) puts("PASS"); 1048 | } 1049 | status = "ok"; 1050 | break; 1051 | case CONVEY_EXIT_FAIL: 1052 | status = "FAIL"; 1053 | if (convey_verbose) { 1054 | (void) puts("FAIL"); 1055 | } 1056 | break; 1057 | default: 1058 | status = "FATAL"; 1059 | if (convey_verbose) { 1060 | (void) puts("FATAL"); 1061 | } 1062 | break; 1063 | } 1064 | 1065 | convey_read_timer(&pc, &secs, &usecs); 1066 | (void) printf( 1067 | "%-8s%-52s%4d.%03ds\n", status, prog, secs, usecs / 1000); 1068 | while ((env = convey_environment) != NULL) { 1069 | convey_environment = env->next; 1070 | free(env); 1071 | } 1072 | convey_read_timer(&pc, &secs, &usecs); 1073 | exit(i); 1074 | } 1075 | -------------------------------------------------------------------------------- /convey.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Garrett D'Amore 3 | * 4 | * This software is supplied under the terms of the MIT License, a 5 | * copy of which should be located in the distribution where this 6 | * file was obtained (LICENSE.txt). A copy of the license may also be 7 | * found online at https://opensource.org/licenses/MIT. 8 | */ 9 | 10 | #ifndef CONVEY_H 11 | 12 | #define CONVEY_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /* 21 | * This test framework allows one to write tests as a form of assertion, 22 | * giving simpler and more readable test logic. 23 | * 24 | * The test framework provides a main() function. 25 | * 26 | * To use this call the Main() macro, and embed Test() and Convey() blocks. 27 | * These can be nested, and after each convey the entire stack is popped so 28 | * that execution can continue from the beginning, giving each test section 29 | * the same environment. 30 | * 31 | * There are assertion macros too, which don't roll back the stack, but which 32 | * do update the test state. 33 | * 34 | * Here's a sample file: 35 | * 36 | * Main({ 37 | * Test({"Integer Tests", { 38 | * int x = 1; int y = 2; 39 | * Convey("Addition works", func() { 40 | * So(y == 2); 41 | * So(y + x == 3); 42 | * So(x + y == 3); 43 | * Convey("Even big numbers", { 44 | * y = 100; 45 | * So(x + y == 101); 46 | * }); 47 | * Convey("Notice y is still 2 in this context", { 48 | * So(y == 2); 49 | * }); 50 | * }); 51 | * }); 52 | * }) 53 | * 54 | * This was inspired by GoConvey -- github.com/smartystreets/goconvey - but 55 | * there are differences of course -- C is not Go! 56 | * 57 | * Pleaes note that we abuse the C preprocessor and setjmp fairly heavily, 58 | * and as a result of the magic we have to do, a lot of these guts must be 59 | * exposed in this header file. HOWEVER, only symbols beginning with a 60 | * capital letter are intended for consumers. All others are for internal 61 | * use only. Otherwise, welcome to the sausage factory. 62 | * 63 | * Please see the documentation at github.com/gdamore/c-convey for more 64 | * details about how to use this. 65 | */ 66 | 67 | /* 68 | * This structure has to be exposed in order to expose the buffer used for 69 | * setjmp. It's members should never be accessed directly. These should be 70 | * allocated statically in the routine(s) that need custom contexts. The 71 | * framework creates a context automatically for each convey scope. 72 | */ 73 | typedef struct { 74 | jmp_buf cs_jmp; 75 | void * cs_data; 76 | } conveyScope; 77 | 78 | /* These functions are not for use by tests -- they are used internally. */ 79 | extern int conveyStart(conveyScope *, const char *); 80 | extern int conveyLoop(conveyScope *, int); 81 | extern void conveyFinish(conveyScope *, int *); 82 | extern int conveyMain(int, char **); 83 | extern char *conveyGetEnv(const char *); 84 | extern int conveyPutEnv(const char *, char *); 85 | 86 | extern void conveyAssertPass(const char *, const char *, int); 87 | extern void conveyAssertSkip(const char *, const char *, int); 88 | extern void conveyAssertFail(const char *, const char *, int); 89 | extern void conveySkip(const char *, int, const char *, ...); 90 | extern void conveyFail(const char *, int, const char *, ...); 91 | extern void conveyError(const char *, int, const char *, ...); 92 | extern void conveyPrintf(const char *, int, const char *, ...); 93 | 94 | /* 95 | * conveyRun is a helper macro not to be called directly by user 96 | * code. It has to be here exposed, in order for setjmp() to work. 97 | * and for the code block to be inlined. Becuase this inlines user 98 | * code, we have to be *very* careful with symbol names. 99 | */ 100 | #define conveyRun(convey_name, convey_code, convey_resultp) \ 101 | do { \ 102 | static conveyScope convey_scope; \ 103 | int convey_unwind; \ 104 | int convey_break = 0; \ 105 | if (conveyStart(&convey_scope, convey_name) != 0) { \ 106 | break; \ 107 | } \ 108 | convey_unwind = setjmp(convey_scope.cs_jmp); \ 109 | if (conveyLoop(&convey_scope, convey_unwind) != 0) { \ 110 | break; \ 111 | } \ 112 | do { \ 113 | convey_code \ 114 | } while (0); \ 115 | if (convey_break) { \ 116 | break; \ 117 | } \ 118 | conveyFinish(&convey_scope, convey_resultp); \ 119 | } while (0); 120 | 121 | /* 122 | * ConveyReset establishes a reset for the current scope. This code will 123 | * be executed every time the current scope is unwinding. This means that 124 | * the code will be executed each time a child convey exits. It is also 125 | * going to be executed once more, for the final pass, which doesn't actually 126 | * execute any convey blocks. (This final pass is required in order to 127 | * learn that all convey's, as well as any code beyond them, are complete.) 128 | * 129 | * The way this works is by overriding the existing scope's jump buffer. 130 | * 131 | * Unlike with GoConvey, this must be registered before any children 132 | * convey blocks; the logic only affects convey blocks that follow this 133 | * one, within the same scope. 134 | * 135 | * This must be in a conveyRun scope (i.e. part of a Convey() or a 136 | * top level Test() or it will not compile. 137 | * 138 | * It is possible to have a subsequent reset at the same convey scope 139 | * override a prior reset. Normally you should avoid this, and just 140 | * use lower level convey blocks. 141 | */ 142 | #define ConveyReset(convey_reset_code) \ 143 | convey_unwind = setjmp(convey_scope.cs_jmp); \ 144 | if (convey_unwind) { \ 145 | do { \ 146 | convey_reset_code \ 147 | } while (0); \ 148 | } \ 149 | if (conveyLoop(&convey_scope, convey_unwind) != 0) { \ 150 | convey_break = 1; \ 151 | break; \ 152 | } 153 | 154 | /* 155 | * ConveyMain is the outer most scope that most test programs use, unless they 156 | * use the short-cut ConveyTestMain. This creates a main() routine that 157 | * sets up the program, parses options, and then executes the tests nested 158 | * within it. 159 | */ 160 | #define ConveyMain(code) \ 161 | static int convey_main_rv; \ 162 | int conveyMainImpl(void) \ 163 | { \ 164 | do { \ 165 | code \ 166 | } while (0); \ 167 | return (convey_main_rv); \ 168 | } \ 169 | int main(int argc, char **argv) { return (conveyMain(argc, argv)); } 170 | 171 | /* 172 | * ConveyGetEnv is used to get environment variables, which can be 173 | * overridden with -p = on the command line. 174 | */ 175 | #define ConveyGetEnv(name) conveyGetEnv(name) 176 | 177 | /* 178 | * ConveyPutEnv is used to change environment variables. This is not 179 | * thread safe! 180 | */ 181 | #define ConveyPutEnv(name, value) conveyPutEnv(name, value) 182 | /* 183 | * ConveyTest creates a top-level test instance, which can contain multiple 184 | * Convey blocks. 185 | */ 186 | #define ConveyTest(name, code) \ 187 | do { \ 188 | int convey_rv; \ 189 | conveyRun(name, code, &convey_rv); \ 190 | if (convey_rv > convey_main_rv) { \ 191 | convey_main_rv = convey_rv; \ 192 | }; \ 193 | } while (0); 194 | 195 | /* 196 | * ConveyTestMain is used to wrap the top-level of your test suite, and is 197 | * used in lieu of a normal main() function. This is the usual case where 198 | * the executable only contains a single top level test group. It 199 | * is the same as using Main with just a single Test embedded, but saves 200 | * some typing and probably a level of indentation. 201 | */ 202 | #define ConveyTestMain(name, code) ConveyMain(ConveyTest(name, code)) 203 | 204 | /* 205 | * EXPERIMENTAL: 206 | * If you don't want to use the test framework's main routine, but 207 | * prefer (or need, because of threading for example) to have your 208 | * test code driven separately, you can use inject ConveyBlock() in 209 | * your function. It works like ConveyMain(). These must not be 210 | * nested within other Conveys, Tests, or Blocks (or Main). The 211 | * results are undefined if you try that. The final result pointer may 212 | * be NULL, or a pointer to an integer to receive the an integer 213 | * result from the test. (0 is success, 4 indicates a failure to allocate 214 | * memory in the test framework, and anything else indicates a 215 | * an error or failure in the code being tested. 216 | * 217 | * Blocks do not contain Tests, rather they contain Conveys only. The 218 | * Block takes the place of both Main() and Test(). It is to be hoped 219 | * that you will not need this. 220 | */ 221 | #define ConveyBlock(name, code, resultp) conveyRun(name, code, resultp) 222 | 223 | /* 224 | * ConveyAssert and ConveySo allow you to run assertions. 225 | */ 226 | #define ConveyAssert(truth) \ 227 | do { \ 228 | if (!(truth)) { \ 229 | conveyAssertFail(#truth, __FILE__, __LINE__); \ 230 | } else { \ 231 | conveyAssertPass(#truth, __FILE__, __LINE__); \ 232 | } \ 233 | } while (0) 234 | 235 | #define ConveySo(truth) ConveyAssert(truth) 236 | 237 | /* 238 | * Convey(name, ) starts a convey context, with as 239 | * the body. The is its scope, and may be called repeatedly 240 | * within the body of a loop. 241 | */ 242 | #define Convey(name, code) conveyRun(name, code, NULL) 243 | 244 | /* 245 | * ConveySkip() just stops processing of the rest of the current context, 246 | * and records that processing was skipped. 247 | */ 248 | 249 | /* 250 | * If your preprocessor doesn't understand C99 variadics, indicate it 251 | * with CONVEY_NO_VARIADICS. In that case you lose support for printf-style 252 | * format specifiers. 253 | */ 254 | #ifdef CONVEY_NO_VARIADICS 255 | #define ConveySkip(reason) conveySkip(__FILE__, __LINE__, reason) 256 | #define ConveyFail(reason) conveyFail(__FILE__, __LINE__, reason) 257 | #define ConveyError(reason) conveyError(__FILE__, __LINE__, reason) 258 | #define ConveyPrintf(reason) conveyPrintf(__FILE__, __LINE__, reason) 259 | #else 260 | #define ConveySkip(...) conveySkip(__FILE__, __LINE__, __VA_ARGS__) 261 | #define ConveyFail(...) conveyFail(__FILE__, __LINE__, __VA_ARGS__) 262 | #define ConveyError(...) conveyError(__FILE__, __LINE__, __VA_ARGS__) 263 | #define ConveyPrintf(...) conveyPrintf(__FILE__, __LINE__, __VA_ARGS__) 264 | #endif 265 | 266 | /* 267 | * ConveySkipSo() is used to skip processing of a single assertion. 268 | * Further processing in the same context continues. 269 | */ 270 | #define ConveySkipAssert(truth) conveyAssertSkip(#truth, __FILE__, __LINE__) 271 | #define ConveySkipSo(truth) ConveySkipAssert(truth) 272 | 273 | /* 274 | * ConveySkipConvey() is used to skip a convey context. This is intended 275 | * to permit changing "Convey", to "SkipConvey". This is logged, 276 | * and the current convey context continues processing. 277 | */ 278 | #define ConveySkipConvey(name, code) \ 279 | conveyRun(name, ConveySkip("Skipped");, NULL) 280 | 281 | /* 282 | * ConveyInit sets up initial things required for testing. If you don't 283 | * use ConveyMain(), then you need to call this somewhere early in your 284 | * main routine. If it returns non-zero, then you can't use the framework. 285 | */ 286 | extern int ConveyInit(void); 287 | 288 | /* 289 | * ConveySetVerbose sets verbose mode. You shouldn't set this normally, 290 | * as the main() wrapper looks at argv, and does if -v is supplied. 291 | */ 292 | extern void ConveySetVerbose(void); 293 | 294 | /* 295 | * These are some public macros intended to make the API more friendly. 296 | * The user is welcome to #undefine any of these he wishes not to 297 | * use, or he can simply avoid the pollution altogether by defining 298 | * CONVEY_NAMESPACE_CLEAN before including this header file. Any 299 | * of these names are already defined using the Convey prefix, with 300 | * the sole exception of Convey() itself, which you cannot undefine. 301 | * (We don't define a ConveyConvey()... that's just silly.) Most of the 302 | * time you won't need this, because its test code that you control, and 303 | * you're writing to Convey(), so you can trivially avoid the conflicts and 304 | * benefit from the friendlier names. This is why this is the default. 305 | * 306 | * There are some other less often used functions that we haven't aliased, 307 | * like ConveyBlock() and ConveySetVerbose(). Aliases for those offer 308 | * little benefit for the extra pollution they would create. 309 | */ 310 | #ifndef CONVEY_NAMESPACE_CLEAN 311 | 312 | #define TestMain ConveyTestMain 313 | #define Test ConveyTest 314 | #define Main ConveyMain 315 | #define So ConveySo 316 | #define Skip ConveySkip 317 | #define Fail ConveyFail 318 | #define Error ConveyError 319 | #define SkipConvey ConveySkipConvey 320 | #define SkipSo ConveySkipSo 321 | #define Reset ConveyReset 322 | #define Printf ConveyPrintf 323 | 324 | #endif /* CONVEY_NAMESPACE_CLEAN */ 325 | 326 | #endif /* CONVEY_H */ 327 | -------------------------------------------------------------------------------- /convey_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Garrett D'Amore 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom 9 | * the Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included 12 | * in all 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 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | /* 24 | * This file is intended to test the framework. It also demonstrates 25 | * some of the capabilities. 26 | */ 27 | 28 | #include "convey.h" 29 | 30 | #include 31 | #include 32 | 33 | Main({ 34 | 35 | /* 36 | * The ordering test demonstrates the execution order. 37 | * At the end of each inner Convey, we roll back up the stack 38 | * to the root, and then start over, bypassing Convey blocks 39 | * that have already completed. Note that if a Convey block 40 | * is the last thing in an enclosing Convey, we will make 41 | * one more pass all the way through until we bypass that last 42 | * item and can close the outer Convey. 43 | */ 44 | Test("Ordering", { 45 | /* 46 | * The buffer has to be static because don't want to clear 47 | * it with each new pass -- that would defeat our tests! 48 | * Note that it starts zeroed (C standard). 49 | */ 50 | static char buffer[32]; 51 | static int bufidx; 52 | 53 | Convey("A runs first", { buffer[bufidx++] = 'A'; }); 54 | Printf("Bufidx is now %d", 1); 55 | buffer[bufidx++] = '1'; 56 | 57 | Convey("B runs after A", { 58 | 59 | So(strlen(buffer) > 0); 60 | So(buffer[bufidx - 1] == '1'); 61 | buffer[bufidx++] = 'B'; 62 | 63 | Convey("C runs inside B", { 64 | So(buffer[bufidx - 1] == 'B'); 65 | buffer[bufidx++] = 'C'; 66 | }); 67 | }); 68 | 69 | Convey("D runs afer A, B, C.", { 70 | So(buffer[bufidx - 1] == '1'); 71 | buffer[bufidx++] = 'D'; 72 | }); 73 | 74 | buffer[bufidx++] = '2'; 75 | 76 | Convey("E is last", { 77 | So(buffer[bufidx - 1] == '2'); 78 | buffer[bufidx++] = 'E'; 79 | }); 80 | 81 | So(strcmp(buffer, "A1BC1B1D12E12") == 0); 82 | }); 83 | 84 | Test("Skipping works", { 85 | int skipped = 0; 86 | SkipConvey("ConveySkip works.", { So(skipped = 0); }); 87 | Convey("Assertion skipping works.", { SkipSo(skipped == 1); }); 88 | }); 89 | 90 | Test("Reset", { 91 | static int x; 92 | 93 | Convey("Initialize X to a non-zero value", { 94 | So(x == 0); 95 | x = 1; 96 | So(x == 1); 97 | }); 98 | 99 | Reset({ x = 20; }); 100 | 101 | Convey("Verify that reset did not get called", { 102 | So(x == 1); 103 | x = 5; 104 | So(x == 5); 105 | }); 106 | 107 | Convey("But now it did", { So(x == 20); }); 108 | }); 109 | 110 | /* save the current status so we can override */ 111 | int oldrv = convey_main_rv; 112 | Test("Failures work", { 113 | Convey("Assertion failure works", { 114 | if (ConveyGetEnv("TEST_FAIL") == NULL) { 115 | Skip("TEST_FAIL environment not set"); 116 | } 117 | Convey("Injected failure", { So(1 == 0); }); 118 | }); 119 | 120 | Convey("ConveyFail works", { 121 | if (ConveyGetEnv("TEST_FAIL") == NULL) { 122 | Skip("TEST_FAIL environment not set"); 123 | } 124 | ConveyFail("forced failure"); 125 | }); 126 | 127 | Convey("ConveyError works", { 128 | if (ConveyGetEnv("TEST_FAIL") == NULL) { 129 | Skip("TEST_FAIL environment not set"); 130 | } 131 | ConveyError("forced error"); 132 | }); 133 | }); 134 | 135 | /* Override the result variable to reset failure. */ 136 | convey_main_rv = oldrv; 137 | 138 | Test("Environment works", { 139 | Convey("PATH environment", { 140 | So(ConveyGetEnv("PATH") != NULL); 141 | So(strlen(ConveyGetEnv("PATH")) != 0); 142 | }); 143 | Convey("Command line args work", { 144 | char *v1 = ConveyGetEnv("ANOTHERNAME"); 145 | char *v2 = ConveyGetEnv("AGAIN"); 146 | if (ConveyGetEnv("NAMETEST") == NULL) { 147 | SkipSo(v1 != NULL); 148 | SkipSo(v2 != NULL); 149 | SkipSo(strcmp(v1, "") == 0); 150 | SkipSo(strcmp(v2, "YES") == 0); 151 | } else { 152 | So(v1 != NULL); 153 | So(v2 != NULL); 154 | So(strcmp(v1, "") == 0); 155 | So(strcmp(v2, "YES") == 0); 156 | } 157 | }) 158 | }); 159 | }) 160 | -------------------------------------------------------------------------------- /demo.c: -------------------------------------------------------------------------------- 1 | #include "convey.h" 2 | 3 | /* 4 | * This is a hypothetical bowling game. In practice your test would 5 | * not have your implementation here, but would probably in some other 6 | * file. For demonstration purposes its included here. 7 | */ 8 | struct frame { 9 | int pins; 10 | int strike; 11 | int spare; 12 | }; 13 | struct game { 14 | struct frame frames[12]; /* includes up to 2 bonus frames */ 15 | int throws; 16 | }; 17 | 18 | void 19 | newGame(struct game *game) 20 | { 21 | for (int i = 0; i < 12; i++) { 22 | game->frames[i].pins = 0; 23 | game->frames[i].strike = 0; 24 | game->frames[i].spare = 0; 25 | } 26 | game->throws = 0; 27 | } 28 | 29 | int 30 | score(struct game *game) 31 | { 32 | int score = 0; 33 | for (int i = 0; i < 10; i++) { 34 | score += game->frames[i].pins; 35 | if (game->frames[i].strike) { 36 | score += game->frames[i+1].pins; 37 | score += game->frames[i+2].pins; 38 | } else if (game->frames[i].spare) { 39 | score += game->frames[i+1].pins; 40 | } 41 | } 42 | return score; 43 | } 44 | 45 | int 46 | gameOver(struct game *game) 47 | { 48 | int maxthrows = 10; 49 | if (game->frames[9].strike) { 50 | maxthrows = 12; 51 | } else if (game->frames[9].spare || game->frames[8].strike) { 52 | maxthrows = 11; 53 | } 54 | return ((game->throws < maxthrows) ? 0 : 1); 55 | } 56 | 57 | /* roll only counts the full numeric score */ 58 | int 59 | roll(struct game *game, int pins) 60 | { 61 | /* Note: special checks for valid bonus frame values not done. */ 62 | if ((pins > 10) || (pins < 0) || gameOver(game)) { 63 | return (-1); 64 | } 65 | game->frames[game->throws++].pins = pins; 66 | return (0); 67 | } 68 | 69 | int 70 | rollStrike(struct game *game) 71 | { 72 | if (gameOver(game)) { 73 | return (-1); 74 | } 75 | game->frames[game->throws].strike = 1; 76 | game->frames[game->throws++].pins = 10; 77 | return (0); 78 | } 79 | 80 | int 81 | rollSpare(struct game *game) 82 | { 83 | if (gameOver(game)) { 84 | return (-1); 85 | } 86 | game->frames[game->throws].spare = 1; 87 | game->frames[game->throws++].pins = 10; 88 | return (0); 89 | } 90 | 91 | int 92 | rollMany(struct game *game, int pins, int count) 93 | { 94 | for (int i = 0; i < count; i++) { 95 | if (roll(game, pins) != 0) { 96 | return (-1); 97 | } 98 | } 99 | return (0); 100 | } 101 | 102 | /* 103 | * This is where the test code starts. 104 | */ 105 | 106 | Main({ 107 | Test("Game Rules", { 108 | Convey("Given a fresh score card", { 109 | struct game game; 110 | newGame(&game); 111 | 112 | Convey("We cannot roll a negative number", { 113 | So(roll(&game, -1) == -1); 114 | }); 115 | Convey("We cannot roll a large number", { 116 | So(roll(&game, 11) == -1); 117 | }); 118 | Convey("We can roll in the middle", { 119 | So(roll(&game, 9) == 0); 120 | }); 121 | Convey("We can roll only 10 frames normally", { 122 | for (int i = 0; i < 10; i++) { 123 | So(roll(&game, 4) == 0); 124 | } 125 | So(roll(&game, 4) == -1); 126 | }); 127 | Convey("We can roll bonus frame if tenth was spare", { 128 | for (int i = 0; i < 10; i++) { 129 | So(rollSpare(&game) == 0); 130 | } 131 | So(roll(&game, 4) == 0); 132 | So(roll(&game, 4) == -1); /* only one bonus */ 133 | }); 134 | 135 | }); 136 | }); 137 | Test("Game Scoring", { 138 | struct game game; 139 | Convey("Given a fresh score card", { 140 | newGame(&game); 141 | 142 | Convey("When all gutter balls are thrown", { 143 | rollMany(&game, 10, 0); 144 | 145 | Convey("The score should be zero", { 146 | So(score(&game) == 0); 147 | }) 148 | }); 149 | 150 | Convey("When all throws knock down one pin", { 151 | rollMany(&game, 10, 1); 152 | 153 | Convey("The score should be 10", { 154 | So(score(&game) == 10); 155 | }) 156 | }); 157 | 158 | Convey("When a spare is thrown", { 159 | rollSpare(&game); 160 | roll(&game, 3); 161 | 162 | Convey("The score includes a spare bonus", { 163 | So(score(&game) == 16); 164 | }); 165 | }); 166 | 167 | Convey("A perfect game is 300 points", { 168 | for (int i = 0; i < 12; i++) { 169 | rollStrike(&game); 170 | } 171 | So(score(&game) == 300); 172 | }); 173 | }); 174 | }); 175 | }) 176 | -------------------------------------------------------------------------------- /screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdamore/c-convey/450e3917afba086520c5d386b5fb6d7bb05f7060/screenshot_1.png -------------------------------------------------------------------------------- /screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdamore/c-convey/450e3917afba086520c5d386b5fb6d7bb05f7060/screenshot_2.png --------------------------------------------------------------------------------