├── .clang-format ├── .github └── workflows │ └── build_and_test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── embedded_cli.c ├── embedded_cli.h ├── embedded_cli_logo.svg ├── examples └── posix_demo.c └── tests ├── acutest.h ├── embedded_cli_fuzzer.c └── embedded_cli_test.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: llvm 2 | IndentWidth: 4 3 | BreakBeforeBraces: Linux 4 | ColumnLimit: 78 5 | AllowShortFunctionsOnASingleLine: None 6 | AllowAllParametersOfDeclarationOnNextLine: false 7 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Build & Test 12 | run: make test 13 | - name: Fuzz 14 | run: make fuzz 15 | - name: Valgrind 16 | run: | 17 | sudo apt update 18 | sudo apt install -y valgrind 19 | valgrind --leak-check=full --error-exitcode=1 ./embedded_cli_test 20 | - name: Test no history 21 | run: | 22 | make clean 23 | make CFLAGS="-DEMBEDDED_CLI_HISTORY_LEN=0 -I." test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | embedded_cli_test 3 | embedded_cli_fuzzer 4 | examples/posix_demo 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. 2 | 3 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-g -Wall -Wextra -Werror -pipe -I. --std=c99 2 | CLANG_FORMAT=clang-format 3 | CLANG?=clang 4 | 5 | SOURCES=embedded_cli.c embedded_cli.h tests/embedded_cli_test.c examples/posix_demo.c tests/embedded_cli_fuzzer.c 6 | 7 | default: examples/posix_demo embedded_cli_test 8 | 9 | test: embedded_cli_test 10 | ./embedded_cli_test 11 | 12 | fuzz: embedded_cli_fuzzer 13 | ./embedded_cli_fuzzer -verbosity=0 -max_total_time=120 -max_len=8192 -rss_limit_mb=1024 14 | 15 | examples/posix_demo: embedded_cli.o examples/posix_demo.o 16 | $(CC) -o $@ $^ 17 | 18 | embedded_cli_test: embedded_cli.o tests/embedded_cli_test.o 19 | $(CC) -o $@ $^ 20 | 21 | embedded_cli_fuzzer: embedded_cli.c tests/embedded_cli_fuzzer.c 22 | $(CLANG) -Itests -I. -g -o $@ tests/embedded_cli_fuzzer.c -fsanitize=fuzzer,address 23 | 24 | %.o: %.c 25 | # cppcheck --quiet --std=c99 --enable=warning,style,performance,portability,information -I. -DTEST_FINI= $< 26 | $(CC) -c -o $@ $< $(CFLAGS) 27 | 28 | format: 29 | $(CLANG_FORMAT) -i $(SOURCES) 30 | 31 | clean: 32 | rm -f *.o */*.o embedded_cli_test embedded_cli_fuzzer examples/posix_demo 33 | rm -f timeout-* crash-* 34 | 35 | .PHONY: clean format test default fuzz 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Embedded CLI 2 | 3 | Embedded CLI Logo 4 | 5 | Embedded CLI is a library for providing usable command line interfaces for embedded systems. It supports history and command line editing. It is designed to use no dynamic memory, and provide an easy to integrate API. 6 | 7 | [![GitHub Actions](https://github.com/AndreRenaud/EmbeddedCLI/workflows/Build%20and%20Test/badge.svg)](https://github.com/AndreRenaud/EmbeddedCLI/actions) 8 | 9 | [![License: BSD 0-Clause](https://img.shields.io/badge/License-BSD%200--Clause-blue.svg)](LICENSE) 10 | 11 | ## Features 12 | * Cursor support (left/right/up/down) 13 | * Searchable history (^R to start search) 14 | * No dynamic allocation 15 | * Base structure has a fixed size, with compile time size limitations 16 | * Comprehensive test suite, including fuzz testing for memory safety 17 | * Command line comprehension 18 | * Support for parsing the command line into an argc/argv pair 19 | * Handling of quoted strings, escaped characters etc... 20 | 21 | Works well in conjunction with the [Simple Options](https://github.com/AndreRenaud/simple_options) library to provide quick & easy argument parsing in embedded environments. Using this combination makes it simple to create an extensible CLI interface, with easy argument parsing/usage/help support. 22 | 23 | ## Platform support & requirements 24 | Embedded CLI makes very few assumptions about the platform. Data input/output is abstracted in call backs. 25 | 26 | Examples are provided for a posix simulator, STM32 27 | 28 | No 3rd party libraries are assumed beyond the following standard C library functions: 29 | * memcpy 30 | * memmove 31 | * memset 32 | * strcmp 33 | * strlen 34 | * strncpy 35 | * strcpy 36 | 37 | All code is C99 compliant. 38 | 39 | ## Memory usage 40 | Memory usage is configurable by adjusting the internal buffering. In the default configuration, it will consume approximately 1.5kB of RAM, most of which is in the history buffer, and 2kB of code space (ARM Thumb2). The easiest way to reduce memory consumption is to drop the support for the history buffer. In this configuration it will consume approximately 200B of RAM, and approximately 1kB of code space (ARM Thumb2). 41 | 42 | ## Thanks 43 | Icon `terminal` by Ashwin Dinesh from the Noun Project 44 | -------------------------------------------------------------------------------- /embedded_cli.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Useful links: 3 | * http://www.asciitable.com 4 | * https://en.wikipedia.org/wiki/ANSI_escape_code 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "embedded_cli.h" 11 | 12 | #define CTRL_R 0x12 13 | 14 | #define CLEAR_EOL "\x1b[0K" 15 | #define MOVE_BOL "\x1b[1G" 16 | 17 | static void cli_putchar(struct embedded_cli *cli, char ch, bool is_last) 18 | { 19 | if (cli->put_char) { 20 | #if EMBEDDED_CLI_SERIAL_XLATE 21 | if (ch == '\n') 22 | cli->put_char(cli->cb_data, '\r', false); 23 | #endif 24 | cli->put_char(cli->cb_data, ch, is_last); 25 | } 26 | } 27 | 28 | static void cli_puts(struct embedded_cli *cli, const char *s) 29 | { 30 | for (; *s; s++) 31 | cli_putchar(cli, *s, s[1] == '\0'); 32 | } 33 | 34 | static void embedded_cli_reset_line(struct embedded_cli *cli) 35 | { 36 | cli->len = 0; 37 | cli->cursor = 0; 38 | cli->counter = 0; 39 | cli->have_csi = cli->have_escape = false; 40 | #if EMBEDDED_CLI_HISTORY_LEN 41 | cli->history_pos = -1; 42 | cli->searching = false; 43 | #endif 44 | } 45 | 46 | void embedded_cli_init(struct embedded_cli *cli, const char *prompt, 47 | void (*put_char)(void *data, char ch, bool is_last), 48 | void *cb_data) 49 | { 50 | memset(cli, 0, sizeof(*cli)); 51 | cli->put_char = put_char; 52 | cli->cb_data = cb_data; 53 | if (prompt) { 54 | strncpy(cli->prompt, prompt, sizeof(cli->prompt)); 55 | cli->prompt[sizeof(cli->prompt) - 1] = '\0'; 56 | } 57 | 58 | embedded_cli_reset_line(cli); 59 | } 60 | 61 | static void cli_ansi(struct embedded_cli *cli, int n, char code) 62 | { 63 | char buffer[5] = {'\x1b', '[', '0' + (n % 10), code, '\0'}; 64 | cli_puts(cli, buffer); 65 | } 66 | 67 | static void term_cursor_back(struct embedded_cli *cli, int n) 68 | { 69 | while (n > 0) { 70 | int count = n > 9 ? 9 : n; 71 | cli_ansi(cli, count, 'D'); 72 | n -= count; 73 | } 74 | } 75 | 76 | static void term_cursor_fwd(struct embedded_cli *cli, int n) 77 | { 78 | while (n > 0) { 79 | int count = n > 9 ? 9 : n; 80 | cli_ansi(cli, count, 'C'); 81 | n -= count; 82 | } 83 | } 84 | 85 | #if EMBEDDED_CLI_HISTORY_LEN 86 | static void term_backspace(struct embedded_cli *cli, int n) 87 | { 88 | // printf("backspace %d ('%s': %d)\n", n, cli->buffer, cli->done); 89 | while (n--) 90 | cli_putchar(cli, '\b', n == 0); 91 | } 92 | 93 | static const char *embedded_cli_get_history_search(struct embedded_cli *cli) 94 | { 95 | for (int i = 0;; i++) { 96 | const char *h = embedded_cli_get_history(cli, i); 97 | if (!h) 98 | return NULL; 99 | if (strstr(h, cli->buffer)) 100 | return h; 101 | } 102 | return NULL; 103 | } 104 | #endif 105 | 106 | static void embedded_cli_insert_default_char(struct embedded_cli *cli, 107 | char ch) 108 | { 109 | // If the buffer is full, there's nothing we can do 110 | if (cli->len >= (int)sizeof(cli->buffer) - 1) 111 | return; 112 | // Insert a gap in the buffer for the new character 113 | memmove(&cli->buffer[cli->cursor + 1], &cli->buffer[cli->cursor], 114 | cli->len - cli->cursor); 115 | cli->buffer[cli->cursor] = ch; 116 | cli->len++; 117 | cli->buffer[cli->len] = '\0'; 118 | cli->cursor++; 119 | 120 | #if EMBEDDED_CLI_HISTORY_LEN 121 | if (cli->searching) { 122 | cli_puts(cli, MOVE_BOL CLEAR_EOL "search:"); 123 | const char *h = embedded_cli_get_history_search(cli); 124 | if (h) 125 | cli_puts(cli, h); 126 | 127 | } else 128 | #endif 129 | { 130 | cli_puts(cli, &cli->buffer[cli->cursor - 1]); 131 | term_cursor_back(cli, cli->len - cli->cursor); 132 | } 133 | } 134 | 135 | const char *embedded_cli_get_history(struct embedded_cli *cli, 136 | int history_pos) 137 | { 138 | #if EMBEDDED_CLI_HISTORY_LEN 139 | int pos = 0; 140 | 141 | if (history_pos < 0) 142 | return NULL; 143 | 144 | // Search back through the history buffer for `history_pos` entry 145 | for (int i = 0; i < history_pos; i++) { 146 | int len = strlen(&cli->history[pos]); 147 | if (len == 0) 148 | return NULL; 149 | pos += len + 1; 150 | if (pos == sizeof(cli->history)) 151 | return NULL; 152 | } 153 | 154 | return &cli->history[pos]; 155 | #else 156 | (void)cli; 157 | (void)history_pos; 158 | return NULL; 159 | #endif 160 | } 161 | 162 | #if EMBEDDED_CLI_HISTORY_LEN 163 | static void embedded_cli_extend_history(struct embedded_cli *cli) 164 | { 165 | int len = strlen(cli->buffer); 166 | if (len > 0) { 167 | // If the new entry is the same as the most recent history entry, 168 | // then don't insert it 169 | if (strcmp(cli->buffer, cli->history) == 0) 170 | return; 171 | memmove(&cli->history[len + 1], &cli->history[0], 172 | sizeof(cli->history) - len + 1); 173 | memcpy(cli->history, cli->buffer, len + 1); 174 | // Make sure it's always nul terminated 175 | cli->history[sizeof(cli->history) - 1] = '\0'; 176 | } 177 | } 178 | 179 | static void embedded_cli_stop_search(struct embedded_cli *cli, bool print) 180 | { 181 | const char *h = embedded_cli_get_history_search(cli); 182 | if (h) { 183 | strncpy(cli->buffer, h, sizeof(cli->buffer)); 184 | cli->buffer[sizeof(cli->buffer) - 1] = '\0'; 185 | } else 186 | cli->buffer[0] = '\0'; 187 | cli->len = cli->cursor = strlen(cli->buffer); 188 | cli->searching = false; 189 | if (print) { 190 | cli_puts(cli, MOVE_BOL CLEAR_EOL); 191 | cli_puts(cli, cli->prompt); 192 | cli_puts(cli, cli->buffer); 193 | } 194 | } 195 | #endif 196 | 197 | bool embedded_cli_insert_char(struct embedded_cli *cli, char ch) 198 | { 199 | // If we're inserting a character just after a finished line, clear things 200 | // up 201 | if (cli->done) { 202 | cli->buffer[0] = '\0'; 203 | cli->done = false; 204 | } 205 | // printf("Inserting char %d 0x%x '%c'\n", ch, ch, ch); 206 | if (cli->have_csi) { 207 | if (ch >= '0' && ch <= '9' && cli->counter < 100) { 208 | cli->counter = cli->counter * 10 + ch - '0'; 209 | // printf("cli->counter -> %d\n", cli->counter); 210 | } else { 211 | if (cli->counter == 0) 212 | cli->counter = 1; 213 | switch (ch) { 214 | case 'A': { // up arrow 215 | #if EMBEDDED_CLI_HISTORY_LEN 216 | // Backspace over our current line 217 | term_backspace(cli, cli->done ? 0 : cli->cursor); 218 | const char *line = 219 | embedded_cli_get_history(cli, cli->history_pos + 1); 220 | if (line) { 221 | int len = strlen(line); 222 | cli->history_pos++; 223 | // printf("history up %d = '%s'\n", cli->history_pos, 224 | // line); 225 | strncpy(cli->buffer, line, sizeof(cli->buffer)); 226 | cli->buffer[sizeof(cli->buffer) - 1] = '\0'; 227 | cli->len = len; 228 | cli->cursor = len; 229 | cli_puts(cli, cli->buffer); 230 | cli_puts(cli, CLEAR_EOL); 231 | } else { 232 | int tmp = cli->history_pos; // We don't want to wrap this 233 | // history, so retain it 234 | cli->buffer[0] = '\0'; 235 | embedded_cli_reset_line(cli); 236 | cli->history_pos = tmp; 237 | cli_puts(cli, CLEAR_EOL); 238 | } 239 | #endif 240 | break; 241 | } 242 | 243 | case 'B': { // down arrow 244 | #if EMBEDDED_CLI_HISTORY_LEN 245 | term_backspace(cli, cli->done ? 0 : cli->cursor); 246 | const char *line = 247 | embedded_cli_get_history(cli, cli->history_pos - 1); 248 | if (line) { 249 | int len = strlen(line); 250 | cli->history_pos--; 251 | // printf("history down %d = '%s'\n", 252 | // cli->history_pos, line); 253 | strncpy(cli->buffer, line, sizeof(cli->buffer)); 254 | cli->buffer[sizeof(cli->buffer) - 1] = '\0'; 255 | cli->len = len; 256 | cli->cursor = len; 257 | cli_puts(cli, cli->buffer); 258 | cli_puts(cli, CLEAR_EOL); 259 | } else { 260 | cli->buffer[0] = '\0'; 261 | embedded_cli_reset_line(cli); 262 | cli_puts(cli, CLEAR_EOL); 263 | } 264 | #endif 265 | break; 266 | } 267 | 268 | case 'C': 269 | if (cli->cursor <= cli->len - cli->counter) { 270 | cli->cursor += cli->counter; 271 | term_cursor_fwd(cli, cli->counter); 272 | } 273 | break; 274 | case 'D': 275 | // printf("back %d vs %d\n", cli->cursor, cli->counter); 276 | if (cli->cursor >= cli->counter) { 277 | cli->cursor -= cli->counter; 278 | term_cursor_back(cli, cli->counter); 279 | } 280 | break; 281 | case 'F': 282 | term_cursor_fwd(cli, cli->len - cli->cursor); 283 | cli->cursor = cli->len; 284 | break; 285 | case 'H': 286 | term_cursor_back(cli, cli->cursor); 287 | cli->cursor = 0; 288 | break; 289 | case '~': 290 | if (cli->counter == 3) { // delete key 291 | if (cli->cursor < cli->len) { 292 | memmove(&cli->buffer[cli->cursor], 293 | &cli->buffer[cli->cursor + 1], 294 | cli->len - cli->cursor); 295 | cli->len--; 296 | cli_puts(cli, &cli->buffer[cli->cursor]); 297 | cli_puts(cli, " "); 298 | term_cursor_back(cli, cli->len - cli->cursor + 1); 299 | } 300 | } 301 | break; 302 | default: 303 | // TODO: Handle more escape sequences 304 | break; 305 | } 306 | cli->have_csi = cli->have_escape = false; 307 | cli->counter = 0; 308 | } 309 | } else { 310 | switch (ch) { 311 | case '\0': 312 | break; 313 | case '\x01': 314 | // Go to the beginning of the line 315 | term_cursor_back(cli, cli->cursor); 316 | cli->cursor = 0; 317 | break; 318 | case '\x03': 319 | cli_puts(cli, "^C\n"); 320 | cli_puts(cli, cli->prompt); 321 | embedded_cli_reset_line(cli); 322 | cli->buffer[0] = '\0'; 323 | break; 324 | case '\x05': // Ctrl-E 325 | term_cursor_fwd(cli, cli->len - cli->cursor); 326 | cli->cursor = cli->len; 327 | break; 328 | case '\x0b': // Ctrl-K 329 | cli_puts(cli, CLEAR_EOL); 330 | cli->buffer[cli->cursor] = '\0'; 331 | cli->len = cli->cursor; 332 | break; 333 | case '\x0c': // Ctrl-L 334 | cli_puts(cli, MOVE_BOL CLEAR_EOL); 335 | cli_puts(cli, cli->prompt); 336 | cli_puts(cli, cli->buffer); 337 | term_cursor_back(cli, cli->len - cli->cursor); 338 | break; 339 | case '\b': // Backspace 340 | case 0x7f: // backspace? 341 | // printf("backspace %d\n", cli->cursor); 342 | #if EMBEDDED_CLI_HISTORY_LEN 343 | if (cli->searching) 344 | embedded_cli_stop_search(cli, true); 345 | #endif 346 | if (cli->cursor > 0) { 347 | memmove(&cli->buffer[cli->cursor - 1], 348 | &cli->buffer[cli->cursor], 349 | cli->len - cli->cursor + 1); 350 | cli->cursor--; 351 | cli->len--; 352 | term_cursor_back(cli, 1); 353 | cli_puts(cli, &cli->buffer[cli->cursor]); 354 | cli_puts(cli, " "); 355 | term_cursor_back(cli, cli->len - cli->cursor + 1); 356 | } 357 | break; 358 | case CTRL_R: 359 | #if EMBEDDED_CLI_HISTORY_LEN 360 | if (!cli->searching) { 361 | cli_puts(cli, "\nsearch:"); 362 | cli->searching = true; 363 | } 364 | #endif 365 | break; 366 | case '\x1b': 367 | #if EMBEDDED_CLI_HISTORY_LEN 368 | if (cli->searching) 369 | embedded_cli_stop_search(cli, true); 370 | #endif 371 | cli->have_csi = false; 372 | cli->have_escape = true; 373 | cli->counter = 0; 374 | break; 375 | case '[': 376 | if (cli->have_escape) 377 | cli->have_csi = true; 378 | else 379 | embedded_cli_insert_default_char(cli, ch); 380 | break; 381 | #if EMBEDDED_CLI_SERIAL_XLATE 382 | case '\r': 383 | ch = '\n'; // So cli->done will exit 384 | #endif 385 | // fallthrough 386 | case '\n': 387 | cli_putchar(cli, '\n', true); 388 | break; 389 | default: 390 | if (ch > 0) 391 | embedded_cli_insert_default_char(cli, ch); 392 | } 393 | } 394 | cli->done = (ch == '\n'); 395 | 396 | if (cli->done) { 397 | #if EMBEDDED_CLI_HISTORY_LEN 398 | if (cli->searching) 399 | embedded_cli_stop_search(cli, false); 400 | embedded_cli_extend_history(cli); 401 | #endif 402 | embedded_cli_reset_line(cli); 403 | } 404 | // printf("Done with char 0x%x (done=%d)\n", ch, cli->done); 405 | return cli->done; 406 | } 407 | 408 | const char *embedded_cli_get_line(const struct embedded_cli *cli) 409 | { 410 | if (!cli->done) 411 | return NULL; 412 | return cli->buffer; 413 | } 414 | 415 | static bool is_whitespace(char ch) 416 | { 417 | return (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); 418 | } 419 | 420 | int embedded_cli_argc(struct embedded_cli *cli, char ***argv) 421 | { 422 | int pos = 0; 423 | bool in_arg = false; 424 | bool in_escape = false; 425 | char in_string = '\0'; 426 | if (!cli->done) 427 | return 0; 428 | for (size_t i = 0; i < sizeof(cli->buffer) && cli->buffer[i] != '\0'; 429 | i++) { 430 | 431 | // If we're escaping this character, just absorb it regardless 432 | if (in_escape) { 433 | in_escape = false; 434 | continue; 435 | } 436 | 437 | if (in_string) { 438 | // If we're finishing a string, blank it out 439 | if (cli->buffer[i] == in_string) { 440 | memmove(&cli->buffer[i], &cli->buffer[i + 1], 441 | sizeof(cli->buffer) - i - 1); 442 | in_string = '\0'; 443 | i--; 444 | } 445 | continue; 446 | } 447 | 448 | // Skip over whitespace, and replace it with nul terminators so 449 | // each argv is nul terminated 450 | if (is_whitespace(cli->buffer[i])) { 451 | if (in_arg) 452 | cli->buffer[i] = '\0'; 453 | in_arg = false; 454 | continue; 455 | } 456 | 457 | if (!in_arg) { 458 | if (pos >= EMBEDDED_CLI_MAX_ARGC) { 459 | break; 460 | } 461 | cli->argv[pos] = &cli->buffer[i]; 462 | pos++; 463 | in_arg = true; 464 | } 465 | 466 | if (cli->buffer[i] == '\\') { 467 | // Absorb the escape character 468 | memmove(&cli->buffer[i], &cli->buffer[i + 1], 469 | sizeof(cli->buffer) - i - 1); 470 | i--; 471 | in_escape = true; 472 | } 473 | 474 | // If we're starting a new string, absorb the character and shuffle 475 | // things back 476 | if (cli->buffer[i] == '\'' || cli->buffer[i] == '"') { 477 | in_string = cli->buffer[i]; 478 | memmove(&cli->buffer[i], &cli->buffer[i + 1], 479 | sizeof(cli->buffer) - i - 1); 480 | i--; 481 | } 482 | } 483 | // Traditionally, there is a NULL entry at argv[argc]. 484 | if (pos >= EMBEDDED_CLI_MAX_ARGC) { 485 | pos--; 486 | } 487 | cli->argv[pos] = NULL; 488 | 489 | *argv = cli->argv; 490 | return pos; 491 | } 492 | 493 | void embedded_cli_prompt(struct embedded_cli *cli) 494 | { 495 | cli_puts(cli, cli->prompt); 496 | } -------------------------------------------------------------------------------- /embedded_cli.h: -------------------------------------------------------------------------------- 1 | #ifndef EMBEDDED_CLI 2 | #define EMBEDDED_CLI 3 | 4 | #include 5 | 6 | #ifndef EMBEDDED_CLI_MAX_LINE 7 | /** 8 | * Maximum number of bytes to accept in a single line 9 | */ 10 | #define EMBEDDED_CLI_MAX_LINE 120 11 | #endif 12 | 13 | #ifndef EMBEDDED_CLI_HISTORY_LEN 14 | /** 15 | * Maximum number of bytes to retain of history data 16 | * Define this to 0 to remove history support 17 | */ 18 | #define EMBEDDED_CLI_HISTORY_LEN 1000 19 | #endif 20 | 21 | #ifndef EMBEDDED_CLI_MAX_ARGC 22 | /** 23 | * What is the maximum number of arguments we reserve space for 24 | */ 25 | #define EMBEDDED_CLI_MAX_ARGC 16 26 | #endif 27 | 28 | #ifndef EMBEDDED_CLI_MAX_PROMPT_LEN 29 | /** 30 | * Maximum number of bytes in the prompt 31 | */ 32 | #define EMBEDDED_CLI_MAX_PROMPT_LEN 10 33 | #endif 34 | 35 | #ifndef EMBEDDED_CLI_SERIAL_XLATE 36 | /** 37 | * Translate CR -> NL on input and output CR NL on output. This allows 38 | * "natural" processing when using a serial terminal. 39 | */ 40 | #define EMBEDDED_CLI_SERIAL_XLATE 1 41 | #endif 42 | 43 | /** 44 | * This is the structure which defines the current state of the CLI 45 | * NOTE: Although this structure is exposed here, it is not recommended 46 | * that it be interacted with directly. Use the accessor functions below to 47 | * interact with it. It is exposed here to make it easier to use as a static 48 | * structure, but all elements of the structure should be considered private 49 | */ 50 | struct embedded_cli { 51 | /** 52 | * Internal buffer. This should not be accessed directly, use the 53 | * access functions below 54 | */ 55 | char buffer[EMBEDDED_CLI_MAX_LINE]; 56 | 57 | #if EMBEDDED_CLI_HISTORY_LEN 58 | /** 59 | * List of history entries 60 | */ 61 | char history[EMBEDDED_CLI_HISTORY_LEN]; 62 | 63 | /** 64 | * Are we searching through the history? 65 | */ 66 | bool searching; 67 | 68 | /** 69 | * How far back in the history are we? 70 | */ 71 | int history_pos; 72 | #endif 73 | 74 | /** 75 | * Number of characters in buffer at the moment 76 | */ 77 | int len; 78 | 79 | /** 80 | * Position of the cursor 81 | */ 82 | int cursor; 83 | 84 | /** 85 | * Have we just parsed a full line? 86 | */ 87 | bool done; 88 | 89 | /** 90 | * Callback function to output a single character to the user 91 | * is_last will be set to true if this is the last character in this 92 | * transmission - this is helpful for flushing buffers. 93 | */ 94 | void (*put_char)(void *data, char ch, bool is_last); 95 | 96 | /** 97 | * Data to provide to the put_char callback 98 | */ 99 | void *cb_data; 100 | 101 | bool have_escape; 102 | bool have_csi; 103 | 104 | /** 105 | * counter of the value for the CSI code 106 | */ 107 | int counter; 108 | 109 | char *argv[EMBEDDED_CLI_MAX_ARGC]; 110 | 111 | char prompt[EMBEDDED_CLI_MAX_PROMPT_LEN]; 112 | }; 113 | 114 | /** 115 | * Start up the Embedded CLI subsystem. This should only be called once. 116 | */ 117 | void embedded_cli_init(struct embedded_cli *, const char *prompt, 118 | void (*put_char)(void *data, char ch, bool is_last), 119 | void *cb_data); 120 | 121 | /** 122 | * Adds a new character into the buffer. Returns true if 123 | * the buffer should now be processed 124 | * Note: This function should not be called from an interrupt handler. 125 | */ 126 | bool embedded_cli_insert_char(struct embedded_cli *cli, char ch); 127 | 128 | /** 129 | * Returns the nul terminated internal buffer. This will 130 | * return NULL if the buffer is not yet complete 131 | */ 132 | const char *embedded_cli_get_line(const struct embedded_cli *cli); 133 | 134 | /** 135 | * Parses the internal buffer and returns it as an argc/argc combo 136 | * @return number of values in argv (maximum of EMBEDDED_CLI_MAX_ARGC) 137 | */ 138 | int embedded_cli_argc(struct embedded_cli *cli, char ***argv); 139 | 140 | /** 141 | * Outputs the CLI prompt 142 | * This should be called after @ref embedded_cli_argc or @ref 143 | * embedded_cli_get_line has been called and the command fully processed 144 | */ 145 | void embedded_cli_prompt(struct embedded_cli *cli); 146 | 147 | /** 148 | * Retrieve a history command line 149 | * @param history_pos 0 is the most recent command, 1 is the one before that 150 | * etc... 151 | * @return NULL if the history buffer is exceeded 152 | */ 153 | const char *embedded_cli_get_history(struct embedded_cli *cli, 154 | int history_pos); 155 | 156 | #endif -------------------------------------------------------------------------------- /embedded_cli_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/posix_demo.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of using EmbeddedCLI in a posix tty environment. 3 | * This is useful as a local test for new functionality 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "embedded_cli.h" 15 | 16 | static struct embedded_cli cli; 17 | 18 | /** 19 | * This function retrieves exactly one character from stdin, 20 | * in character-by-character mode (as opposed to reading a full line) 21 | */ 22 | static char getch(void) 23 | { 24 | char buf = 0; 25 | struct termios old = {0}; 26 | if (tcgetattr(0, &old) < 0) 27 | perror("tcsetattr()"); 28 | 29 | struct termios raw = old; 30 | 31 | // Do what cfmakeraw does (Using --std=c99 means that cfmakeraw isn't 32 | // available) 33 | raw.c_iflag &= 34 | ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); 35 | raw.c_oflag &= ~OPOST; 36 | raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 37 | raw.c_cflag &= ~(CSIZE | PARENB); 38 | raw.c_cflag |= CS8; 39 | 40 | raw.c_cc[VMIN] = 1; 41 | raw.c_cc[VTIME] = 0; 42 | 43 | if (tcsetattr(0, TCSANOW, &raw) < 0) 44 | perror("tcsetattr ICANON"); 45 | 46 | if (read(0, &buf, 1) < 0) 47 | perror("read()"); 48 | 49 | if (tcsetattr(0, TCSADRAIN, &old) < 0) 50 | perror("tcsetattr ~ICANON"); 51 | return (buf); 52 | } 53 | 54 | static void intHandler(int dummy) 55 | { 56 | (void)dummy; 57 | embedded_cli_insert_char(&cli, '\x03'); 58 | } 59 | 60 | /** 61 | * This function outputs a single character to stdout, to be used as the 62 | * callback from embedded cli 63 | */ 64 | static void posix_putch(void *data, char ch, bool is_last) 65 | { 66 | FILE *fp = data; 67 | fputc(ch, fp); 68 | if (is_last) 69 | fflush(fp); 70 | } 71 | 72 | int main(void) 73 | { 74 | bool done = false; 75 | 76 | /** 77 | * Start up the Embedded CLI instance with the appropriate 78 | * callbacks/userdata 79 | */ 80 | embedded_cli_init(&cli, "POSIX> ", posix_putch, stdout); 81 | embedded_cli_prompt(&cli); 82 | 83 | /* Capture Ctrl-C */ 84 | signal(SIGINT, intHandler); 85 | 86 | while (!done) { 87 | char ch = getch(); 88 | 89 | /** 90 | * If we have entered a command, try and process it 91 | */ 92 | if (embedded_cli_insert_char(&cli, ch)) { 93 | int cli_argc; 94 | char **cli_argv; 95 | cli_argc = embedded_cli_argc(&cli, &cli_argv); 96 | printf("Got %d args\n", cli_argc); 97 | for (int i = 0; i < cli_argc; i++) { 98 | printf("Arg %d/%d: '%s'\n", i, cli_argc, cli_argv[i]); 99 | } 100 | done = cli_argc >= 1 && (strcmp(cli_argv[0], "quit") == 0); 101 | 102 | if (!done) 103 | embedded_cli_prompt(&cli); 104 | } 105 | } 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /tests/acutest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Acutest -- Another C/C++ Unit Test facility 3 | * 4 | * 5 | * Copyright 2013-2020 Martin Mitas 6 | * Copyright 2019 Garrett D'Amore 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the "Software"), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the 13 | * Software is furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #ifndef ACUTEST_H 28 | #define ACUTEST_H 29 | 30 | 31 | /************************ 32 | *** Public interface *** 33 | ************************/ 34 | 35 | /* By default, "acutest.h" provides the main program entry point (function 36 | * main()). However, if the test suite is composed of multiple source files 37 | * which include "acutest.h", then this causes a problem of multiple main() 38 | * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all 39 | * compilation units but one. 40 | */ 41 | 42 | /* Macro to specify list of unit tests in the suite. 43 | * The unit test implementation MUST provide list of unit tests it implements 44 | * with this macro: 45 | * 46 | * TEST_LIST = { 47 | * { "test1_name", test1_func_ptr }, 48 | * { "test2_name", test2_func_ptr }, 49 | * ... 50 | * { NULL, NULL } // zeroed record marking the end of the list 51 | * }; 52 | * 53 | * The list specifies names of each test (must be unique) and pointer to 54 | * a function implementing it. The function does not take any arguments 55 | * and has no return values, i.e. every test function has to be compatible 56 | * with this prototype: 57 | * 58 | * void test_func(void); 59 | * 60 | * Note the list has to be ended with a zeroed record. 61 | */ 62 | #define TEST_LIST const struct acutest_test_ acutest_list_[] 63 | 64 | 65 | /* Macros for testing whether an unit test succeeds or fails. These macros 66 | * can be used arbitrarily in functions implementing the unit tests. 67 | * 68 | * If any condition fails throughout execution of a test, the test fails. 69 | * 70 | * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows 71 | * also to specify an error message to print out if the condition fails. 72 | * (It expects printf-like format string and its parameters). The macros 73 | * return non-zero (condition passes) or 0 (condition fails). 74 | * 75 | * That can be useful when more conditions should be checked only if some 76 | * preceding condition passes, as illustrated in this code snippet: 77 | * 78 | * SomeStruct* ptr = allocate_some_struct(); 79 | * if(TEST_CHECK(ptr != NULL)) { 80 | * TEST_CHECK(ptr->member1 < 100); 81 | * TEST_CHECK(ptr->member2 > 200); 82 | * } 83 | */ 84 | #define TEST_CHECK_(cond,...) acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__) 85 | #define TEST_CHECK(cond) acutest_check_((cond), __FILE__, __LINE__, "%s", #cond) 86 | 87 | 88 | /* These macros are the same as TEST_CHECK_ and TEST_CHECK except that if the 89 | * condition fails, the currently executed unit test is immediately aborted. 90 | * 91 | * That is done either by calling abort() if the unit test is executed as a 92 | * child process; or via longjmp() if the unit test is executed within the 93 | * main Acutest process. 94 | * 95 | * As a side effect of such abortion, your unit tests may cause memory leaks, 96 | * unflushed file descriptors, and other phenomena caused by the abortion. 97 | * 98 | * Therefore you should not use these as a general replacement for TEST_CHECK. 99 | * Use it with some caution, especially if your test causes some other side 100 | * effects to the outside world (e.g. communicating with some server, inserting 101 | * into a database etc.). 102 | */ 103 | #define TEST_ASSERT_(cond,...) \ 104 | do { \ 105 | if(!acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__)) \ 106 | acutest_abort_(); \ 107 | } while(0) 108 | #define TEST_ASSERT(cond) \ 109 | do { \ 110 | if(!acutest_check_((cond), __FILE__, __LINE__, "%s", #cond)) \ 111 | acutest_abort_(); \ 112 | } while(0) 113 | 114 | 115 | #ifdef __cplusplus 116 | /* Macros to verify that the code (the 1st argument) throws exception of given 117 | * type (the 2nd argument). (Note these macros are only available in C++.) 118 | * 119 | * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like 120 | * message. 121 | * 122 | * For example: 123 | * 124 | * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType); 125 | * 126 | * If the function_that_throw() throws ExpectedExceptionType, the check passes. 127 | * If the function throws anything incompatible with ExpectedExceptionType 128 | * (or if it does not thrown an exception at all), the check fails. 129 | */ 130 | #define TEST_EXCEPTION(code, exctype) \ 131 | do { \ 132 | bool exc_ok_ = false; \ 133 | const char *msg_ = NULL; \ 134 | try { \ 135 | code; \ 136 | msg_ = "No exception thrown."; \ 137 | } catch(exctype const&) { \ 138 | exc_ok_= true; \ 139 | } catch(...) { \ 140 | msg_ = "Unexpected exception thrown."; \ 141 | } \ 142 | acutest_check_(exc_ok_, __FILE__, __LINE__, #code " throws " #exctype);\ 143 | if(msg_ != NULL) \ 144 | acutest_message_("%s", msg_); \ 145 | } while(0) 146 | #define TEST_EXCEPTION_(code, exctype, ...) \ 147 | do { \ 148 | bool exc_ok_ = false; \ 149 | const char *msg_ = NULL; \ 150 | try { \ 151 | code; \ 152 | msg_ = "No exception thrown."; \ 153 | } catch(exctype const&) { \ 154 | exc_ok_= true; \ 155 | } catch(...) { \ 156 | msg_ = "Unexpected exception thrown."; \ 157 | } \ 158 | acutest_check_(exc_ok_, __FILE__, __LINE__, __VA_ARGS__); \ 159 | if(msg_ != NULL) \ 160 | acutest_message_("%s", msg_); \ 161 | } while(0) 162 | #endif /* #ifdef __cplusplus */ 163 | 164 | 165 | /* Sometimes it is useful to split execution of more complex unit tests to some 166 | * smaller parts and associate those parts with some names. 167 | * 168 | * This is especially handy if the given unit test is implemented as a loop 169 | * over some vector of multiple testing inputs. Using these macros allow to use 170 | * sort of subtitle for each iteration of the loop (e.g. outputting the input 171 | * itself or a name associated to it), so that if any TEST_CHECK condition 172 | * fails in the loop, it can be easily seen which iteration triggers the 173 | * failure, without the need to manually output the iteration-specific data in 174 | * every single TEST_CHECK inside the loop body. 175 | * 176 | * TEST_CASE allows to specify only single string as the name of the case, 177 | * TEST_CASE_ provides all the power of printf-like string formatting. 178 | * 179 | * Note that the test cases cannot be nested. Starting a new test case ends 180 | * implicitly the previous one. To end the test case explicitly (e.g. to end 181 | * the last test case after exiting the loop), you may use TEST_CASE(NULL). 182 | */ 183 | #define TEST_CASE_(...) acutest_case_(__VA_ARGS__) 184 | #define TEST_CASE(name) acutest_case_("%s", name) 185 | 186 | 187 | /* Maximal output per TEST_CASE call. Longer messages are cut. 188 | * You may define another limit prior including "acutest.h" 189 | */ 190 | #ifndef TEST_CASE_MAXSIZE 191 | #define TEST_CASE_MAXSIZE 64 192 | #endif 193 | 194 | 195 | /* printf-like macro for outputting an extra information about a failure. 196 | * 197 | * Intended use is to output some computed output versus the expected value, 198 | * e.g. like this: 199 | * 200 | * if(!TEST_CHECK(produced == expected)) { 201 | * TEST_MSG("Expected: %d", expected); 202 | * TEST_MSG("Produced: %d", produced); 203 | * } 204 | * 205 | * Note the message is only written down if the most recent use of any checking 206 | * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed. 207 | * This means the above is equivalent to just this: 208 | * 209 | * TEST_CHECK(produced == expected); 210 | * TEST_MSG("Expected: %d", expected); 211 | * TEST_MSG("Produced: %d", produced); 212 | * 213 | * The macro can deal with multi-line output fairly well. It also automatically 214 | * adds a final new-line if there is none present. 215 | */ 216 | #define TEST_MSG(...) acutest_message_(__VA_ARGS__) 217 | 218 | 219 | /* Maximal output per TEST_MSG call. Longer messages are cut. 220 | * You may define another limit prior including "acutest.h" 221 | */ 222 | #ifndef TEST_MSG_MAXSIZE 223 | #define TEST_MSG_MAXSIZE 1024 224 | #endif 225 | 226 | 227 | /* Macro for dumping a block of memory. 228 | * 229 | * Its intended use is very similar to what TEST_MSG is for, but instead of 230 | * generating any printf-like message, this is for dumping raw block of a 231 | * memory in a hexadecimal form: 232 | * 233 | * TEST_CHECK(size_produced == size_expected && 234 | * memcmp(addr_produced, addr_expected, size_produced) == 0); 235 | * TEST_DUMP("Expected:", addr_expected, size_expected); 236 | * TEST_DUMP("Produced:", addr_produced, size_produced); 237 | */ 238 | #define TEST_DUMP(title, addr, size) acutest_dump_(title, addr, size) 239 | 240 | /* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut. 241 | * You may define another limit prior including "acutest.h" 242 | */ 243 | #ifndef TEST_DUMP_MAXSIZE 244 | #define TEST_DUMP_MAXSIZE 1024 245 | #endif 246 | 247 | 248 | /* Common test initialiation/clean-up 249 | * 250 | * In some test suites, it may be needed to perform some sort of the same 251 | * initialization and/or clean-up in all the tests. 252 | * 253 | * Such test suites may use macros TEST_INIT and/or TEST_FINI prior including 254 | * this header. The expansion of the macro is then used as a body of helper 255 | * function called just before executing every single (TEST_INIT) or just after 256 | * it ends (TEST_FINI). 257 | * 258 | * Examples of various ways how to use the macro TEST_INIT: 259 | * 260 | * #define TEST_INIT my_init_func(); 261 | * #define TEST_INIT my_init_func() // Works even without the semicolon 262 | * #define TEST_INIT setlocale(LC_ALL, NULL); 263 | * #define TEST_INIT { setlocale(LC_ALL, NULL); my_init_func(); } 264 | * 265 | * TEST_FINI is to be used in the same way. 266 | */ 267 | 268 | 269 | /********************** 270 | *** Implementation *** 271 | **********************/ 272 | 273 | /* The unit test files should not rely on anything below. */ 274 | 275 | #include 276 | #include 277 | #include 278 | #include 279 | #include 280 | #include 281 | 282 | #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) 283 | #define ACUTEST_UNIX_ 1 284 | #include 285 | #include 286 | #include 287 | #include 288 | #include 289 | #include 290 | #include 291 | 292 | #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC 293 | #define ACUTEST_HAS_POSIX_TIMER_ 1 294 | #endif 295 | #endif 296 | 297 | #if defined(_gnu_linux_) || defined(__linux__) 298 | #define ACUTEST_LINUX_ 1 299 | #include 300 | #include 301 | #endif 302 | 303 | #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) 304 | #define ACUTEST_WIN_ 1 305 | #include 306 | #include 307 | #endif 308 | 309 | #ifdef __cplusplus 310 | #include 311 | #endif 312 | 313 | #ifdef __has_include 314 | #if __has_include() 315 | #include 316 | #endif 317 | #endif 318 | 319 | /* Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers */ 320 | #if defined(__GNUC__) || defined(__clang__) 321 | #define ACUTEST_ATTRIBUTE_(attr) __attribute__((attr)) 322 | #else 323 | #define ACUTEST_ATTRIBUTE_(attr) 324 | #endif 325 | 326 | /* Note our global private identifiers end with '_' to mitigate risk of clash 327 | * with the unit tests implementation. */ 328 | 329 | #ifdef __cplusplus 330 | extern "C" { 331 | #endif 332 | 333 | #ifdef _MSC_VER 334 | /* In the multi-platform code like ours, we cannot use the non-standard 335 | * "safe" functions from Microsoft C lib like e.g. sprintf_s() instead of 336 | * standard sprintf(). Hence, lets disable the warning C4996. */ 337 | #pragma warning(push) 338 | #pragma warning(disable: 4996) 339 | #endif 340 | 341 | 342 | struct acutest_test_ { 343 | const char* name; 344 | void (*func)(void); 345 | }; 346 | 347 | struct acutest_test_data_ { 348 | unsigned char flags; 349 | double duration; 350 | }; 351 | 352 | enum { 353 | ACUTEST_FLAG_RUN_ = 1 << 0, 354 | ACUTEST_FLAG_SUCCESS_ = 1 << 1, 355 | ACUTEST_FLAG_FAILURE_ = 1 << 2, 356 | }; 357 | 358 | extern const struct acutest_test_ acutest_list_[]; 359 | 360 | int acutest_check_(int cond, const char* file, int line, const char* fmt, ...); 361 | void acutest_case_(const char* fmt, ...); 362 | void acutest_message_(const char* fmt, ...); 363 | void acutest_dump_(const char* title, const void* addr, size_t size); 364 | void acutest_abort_(void) ACUTEST_ATTRIBUTE_(noreturn); 365 | 366 | 367 | #ifndef TEST_NO_MAIN 368 | 369 | static char* acutest_argv0_ = NULL; 370 | static size_t acutest_list_size_ = 0; 371 | static struct acutest_test_data_* acutest_test_data_ = NULL; 372 | static size_t acutest_count_ = 0; 373 | static int acutest_no_exec_ = -1; 374 | static int acutest_no_summary_ = 0; 375 | static int acutest_tap_ = 0; 376 | static int acutest_skip_mode_ = 0; 377 | static int acutest_worker_ = 0; 378 | static int acutest_worker_index_ = 0; 379 | static int acutest_cond_failed_ = 0; 380 | static int acutest_was_aborted_ = 0; 381 | static FILE *acutest_xml_output_ = NULL; 382 | 383 | static int acutest_stat_failed_units_ = 0; 384 | static int acutest_stat_run_units_ = 0; 385 | 386 | static const struct acutest_test_* acutest_current_test_ = NULL; 387 | static int acutest_current_index_ = 0; 388 | static char acutest_case_name_[TEST_CASE_MAXSIZE] = ""; 389 | static int acutest_test_already_logged_ = 0; 390 | static int acutest_case_already_logged_ = 0; 391 | static int acutest_verbose_level_ = 2; 392 | static int acutest_test_failures_ = 0; 393 | static int acutest_colorize_ = 0; 394 | static int acutest_timer_ = 0; 395 | 396 | static int acutest_abort_has_jmp_buf_ = 0; 397 | static jmp_buf acutest_abort_jmp_buf_; 398 | 399 | 400 | static void 401 | acutest_cleanup_(void) 402 | { 403 | free((void*) acutest_test_data_); 404 | } 405 | 406 | static void ACUTEST_ATTRIBUTE_(noreturn) 407 | acutest_exit_(int exit_code) 408 | { 409 | acutest_cleanup_(); 410 | exit(exit_code); 411 | } 412 | 413 | #if defined ACUTEST_WIN_ 414 | typedef LARGE_INTEGER acutest_timer_type_; 415 | static LARGE_INTEGER acutest_timer_freq_; 416 | static acutest_timer_type_ acutest_timer_start_; 417 | static acutest_timer_type_ acutest_timer_end_; 418 | 419 | static void 420 | acutest_timer_init_(void) 421 | { 422 | QueryPerformanceFrequency(´st_timer_freq_); 423 | } 424 | 425 | static void 426 | acutest_timer_get_time_(LARGE_INTEGER* ts) 427 | { 428 | QueryPerformanceCounter(ts); 429 | } 430 | 431 | static double 432 | acutest_timer_diff_(LARGE_INTEGER start, LARGE_INTEGER end) 433 | { 434 | double duration = (double)(end.QuadPart - start.QuadPart); 435 | duration /= (double)acutest_timer_freq_.QuadPart; 436 | return duration; 437 | } 438 | 439 | static void 440 | acutest_timer_print_diff_(void) 441 | { 442 | printf("%.6lf secs", acutest_timer_diff_(acutest_timer_start_, acutest_timer_end_)); 443 | } 444 | #elif defined ACUTEST_HAS_POSIX_TIMER_ 445 | static clockid_t acutest_timer_id_; 446 | typedef struct timespec acutest_timer_type_; 447 | static acutest_timer_type_ acutest_timer_start_; 448 | static acutest_timer_type_ acutest_timer_end_; 449 | 450 | static void 451 | acutest_timer_init_(void) 452 | { 453 | if(acutest_timer_ == 1) 454 | acutest_timer_id_ = CLOCK_MONOTONIC; 455 | else if(acutest_timer_ == 2) 456 | acutest_timer_id_ = CLOCK_PROCESS_CPUTIME_ID; 457 | } 458 | 459 | static void 460 | acutest_timer_get_time_(struct timespec* ts) 461 | { 462 | clock_gettime(acutest_timer_id_, ts); 463 | } 464 | 465 | static double 466 | acutest_timer_diff_(struct timespec start, struct timespec end) 467 | { 468 | double endns; 469 | double startns; 470 | 471 | endns = end.tv_sec; 472 | endns *= 1e9; 473 | endns += end.tv_nsec; 474 | 475 | startns = start.tv_sec; 476 | startns *= 1e9; 477 | startns += start.tv_nsec; 478 | 479 | return ((endns - startns)/ 1e9); 480 | } 481 | 482 | static void 483 | acutest_timer_print_diff_(void) 484 | { 485 | printf("%.6lf secs", 486 | acutest_timer_diff_(acutest_timer_start_, acutest_timer_end_)); 487 | } 488 | #else 489 | typedef int acutest_timer_type_; 490 | static acutest_timer_type_ acutest_timer_start_; 491 | static acutest_timer_type_ acutest_timer_end_; 492 | 493 | void 494 | acutest_timer_init_(void) 495 | {} 496 | 497 | static void 498 | acutest_timer_get_time_(int* ts) 499 | { 500 | (void) ts; 501 | } 502 | 503 | static double 504 | acutest_timer_diff_(int start, int end) 505 | { 506 | (void) start; 507 | (void) end; 508 | return 0.0; 509 | } 510 | 511 | static void 512 | acutest_timer_print_diff_(void) 513 | {} 514 | #endif 515 | 516 | #define ACUTEST_COLOR_DEFAULT_ 0 517 | #define ACUTEST_COLOR_GREEN_ 1 518 | #define ACUTEST_COLOR_RED_ 2 519 | #define ACUTEST_COLOR_DEFAULT_INTENSIVE_ 3 520 | #define ACUTEST_COLOR_GREEN_INTENSIVE_ 4 521 | #define ACUTEST_COLOR_RED_INTENSIVE_ 5 522 | 523 | static int ACUTEST_ATTRIBUTE_(format (printf, 2, 3)) 524 | acutest_colored_printf_(int color, const char* fmt, ...) 525 | { 526 | va_list args; 527 | char buffer[256]; 528 | int n; 529 | 530 | va_start(args, fmt); 531 | vsnprintf(buffer, sizeof(buffer), fmt, args); 532 | va_end(args); 533 | buffer[sizeof(buffer)-1] = '\0'; 534 | 535 | if(!acutest_colorize_) { 536 | return printf("%s", buffer); 537 | } 538 | 539 | #if defined ACUTEST_UNIX_ 540 | { 541 | const char* col_str; 542 | switch(color) { 543 | case ACUTEST_COLOR_GREEN_: col_str = "\033[0;32m"; break; 544 | case ACUTEST_COLOR_RED_: col_str = "\033[0;31m"; break; 545 | case ACUTEST_COLOR_GREEN_INTENSIVE_: col_str = "\033[1;32m"; break; 546 | case ACUTEST_COLOR_RED_INTENSIVE_: col_str = "\033[1;31m"; break; 547 | case ACUTEST_COLOR_DEFAULT_INTENSIVE_: col_str = "\033[1m"; break; 548 | default: col_str = "\033[0m"; break; 549 | } 550 | printf("%s", col_str); 551 | n = printf("%s", buffer); 552 | printf("\033[0m"); 553 | return n; 554 | } 555 | #elif defined ACUTEST_WIN_ 556 | { 557 | HANDLE h; 558 | CONSOLE_SCREEN_BUFFER_INFO info; 559 | WORD attr; 560 | 561 | h = GetStdHandle(STD_OUTPUT_HANDLE); 562 | GetConsoleScreenBufferInfo(h, &info); 563 | 564 | switch(color) { 565 | case ACUTEST_COLOR_GREEN_: attr = FOREGROUND_GREEN; break; 566 | case ACUTEST_COLOR_RED_: attr = FOREGROUND_RED; break; 567 | case ACUTEST_COLOR_GREEN_INTENSIVE_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; 568 | case ACUTEST_COLOR_RED_INTENSIVE_: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; 569 | case ACUTEST_COLOR_DEFAULT_INTENSIVE_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; 570 | default: attr = 0; break; 571 | } 572 | if(attr != 0) 573 | SetConsoleTextAttribute(h, attr); 574 | n = printf("%s", buffer); 575 | SetConsoleTextAttribute(h, info.wAttributes); 576 | return n; 577 | } 578 | #else 579 | n = printf("%s", buffer); 580 | return n; 581 | #endif 582 | } 583 | 584 | static void 585 | acutest_begin_test_line_(const struct acutest_test_* test) 586 | { 587 | if(!acutest_tap_) { 588 | if(acutest_verbose_level_ >= 3) { 589 | acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Test %s:\n", test->name); 590 | acutest_test_already_logged_++; 591 | } else if(acutest_verbose_level_ >= 1) { 592 | int n; 593 | char spaces[48]; 594 | 595 | n = acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Test %s... ", test->name); 596 | memset(spaces, ' ', sizeof(spaces)); 597 | if(n < (int) sizeof(spaces)) 598 | printf("%.*s", (int) sizeof(spaces) - n, spaces); 599 | } else { 600 | acutest_test_already_logged_ = 1; 601 | } 602 | } 603 | } 604 | 605 | static void 606 | acutest_finish_test_line_(int result) 607 | { 608 | if(acutest_tap_) { 609 | const char* str = (result == 0) ? "ok" : "not ok"; 610 | 611 | printf("%s %d - %s\n", str, acutest_current_index_ + 1, acutest_current_test_->name); 612 | 613 | if(result == 0 && acutest_timer_) { 614 | printf("# Duration: "); 615 | acutest_timer_print_diff_(); 616 | printf("\n"); 617 | } 618 | } else { 619 | int color = (result == 0) ? ACUTEST_COLOR_GREEN_INTENSIVE_ : ACUTEST_COLOR_RED_INTENSIVE_; 620 | const char* str = (result == 0) ? "OK" : "FAILED"; 621 | printf("[ "); 622 | acutest_colored_printf_(color, "%s", str); 623 | printf(" ]"); 624 | 625 | if(result == 0 && acutest_timer_) { 626 | printf(" "); 627 | acutest_timer_print_diff_(); 628 | } 629 | 630 | printf("\n"); 631 | } 632 | } 633 | 634 | static void 635 | acutest_line_indent_(int level) 636 | { 637 | static const char spaces[] = " "; 638 | int n = level * 2; 639 | 640 | if(acutest_tap_ && n > 0) { 641 | n--; 642 | printf("#"); 643 | } 644 | 645 | while(n > 16) { 646 | printf("%s", spaces); 647 | n -= 16; 648 | } 649 | printf("%.*s", n, spaces); 650 | } 651 | 652 | int ACUTEST_ATTRIBUTE_(format (printf, 4, 5)) 653 | acutest_check_(int cond, const char* file, int line, const char* fmt, ...) 654 | { 655 | const char *result_str; 656 | int result_color; 657 | int verbose_level; 658 | 659 | if(cond) { 660 | result_str = "ok"; 661 | result_color = ACUTEST_COLOR_GREEN_; 662 | verbose_level = 3; 663 | } else { 664 | if(!acutest_test_already_logged_ && acutest_current_test_ != NULL) 665 | acutest_finish_test_line_(-1); 666 | 667 | result_str = "failed"; 668 | result_color = ACUTEST_COLOR_RED_; 669 | verbose_level = 2; 670 | acutest_test_failures_++; 671 | acutest_test_already_logged_++; 672 | } 673 | 674 | if(acutest_verbose_level_ >= verbose_level) { 675 | va_list args; 676 | 677 | if(!acutest_case_already_logged_ && acutest_case_name_[0]) { 678 | acutest_line_indent_(1); 679 | acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", acutest_case_name_); 680 | acutest_test_already_logged_++; 681 | acutest_case_already_logged_++; 682 | } 683 | 684 | acutest_line_indent_(acutest_case_name_[0] ? 2 : 1); 685 | if(file != NULL) { 686 | #ifdef ACUTEST_WIN_ 687 | const char* lastsep1 = strrchr(file, '\\'); 688 | const char* lastsep2 = strrchr(file, '/'); 689 | if(lastsep1 == NULL) 690 | lastsep1 = file-1; 691 | if(lastsep2 == NULL) 692 | lastsep2 = file-1; 693 | file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; 694 | #else 695 | const char* lastsep = strrchr(file, '/'); 696 | if(lastsep != NULL) 697 | file = lastsep+1; 698 | #endif 699 | printf("%s:%d: Check ", file, line); 700 | } 701 | 702 | va_start(args, fmt); 703 | vprintf(fmt, args); 704 | va_end(args); 705 | 706 | printf("... "); 707 | acutest_colored_printf_(result_color, "%s", result_str); 708 | printf("\n"); 709 | acutest_test_already_logged_++; 710 | } 711 | 712 | acutest_cond_failed_ = (cond == 0); 713 | return !acutest_cond_failed_; 714 | } 715 | 716 | void ACUTEST_ATTRIBUTE_(format (printf, 1, 2)) 717 | acutest_case_(const char* fmt, ...) 718 | { 719 | va_list args; 720 | 721 | if(acutest_verbose_level_ < 2) 722 | return; 723 | 724 | if(acutest_case_name_[0]) { 725 | acutest_case_already_logged_ = 0; 726 | acutest_case_name_[0] = '\0'; 727 | } 728 | 729 | if(fmt == NULL) 730 | return; 731 | 732 | va_start(args, fmt); 733 | vsnprintf(acutest_case_name_, sizeof(acutest_case_name_) - 1, fmt, args); 734 | va_end(args); 735 | acutest_case_name_[sizeof(acutest_case_name_) - 1] = '\0'; 736 | 737 | if(acutest_verbose_level_ >= 3) { 738 | acutest_line_indent_(1); 739 | acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", acutest_case_name_); 740 | acutest_test_already_logged_++; 741 | acutest_case_already_logged_++; 742 | } 743 | } 744 | 745 | void ACUTEST_ATTRIBUTE_(format (printf, 1, 2)) 746 | acutest_message_(const char* fmt, ...) 747 | { 748 | char buffer[TEST_MSG_MAXSIZE]; 749 | char* line_beg; 750 | char* line_end; 751 | va_list args; 752 | 753 | if(acutest_verbose_level_ < 2) 754 | return; 755 | 756 | /* We allow extra message only when something is already wrong in the 757 | * current test. */ 758 | if(acutest_current_test_ == NULL || !acutest_cond_failed_) 759 | return; 760 | 761 | va_start(args, fmt); 762 | vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args); 763 | va_end(args); 764 | buffer[TEST_MSG_MAXSIZE-1] = '\0'; 765 | 766 | line_beg = buffer; 767 | while(1) { 768 | line_end = strchr(line_beg, '\n'); 769 | if(line_end == NULL) 770 | break; 771 | acutest_line_indent_(acutest_case_name_[0] ? 3 : 2); 772 | printf("%.*s\n", (int)(line_end - line_beg), line_beg); 773 | line_beg = line_end + 1; 774 | } 775 | if(line_beg[0] != '\0') { 776 | acutest_line_indent_(acutest_case_name_[0] ? 3 : 2); 777 | printf("%s\n", line_beg); 778 | } 779 | } 780 | 781 | void 782 | acutest_dump_(const char* title, const void* addr, size_t size) 783 | { 784 | static const size_t BYTES_PER_LINE = 16; 785 | size_t line_beg; 786 | size_t truncate = 0; 787 | 788 | if(acutest_verbose_level_ < 2) 789 | return; 790 | 791 | /* We allow extra message only when something is already wrong in the 792 | * current test. */ 793 | if(acutest_current_test_ == NULL || !acutest_cond_failed_) 794 | return; 795 | 796 | if(size > TEST_DUMP_MAXSIZE) { 797 | truncate = size - TEST_DUMP_MAXSIZE; 798 | size = TEST_DUMP_MAXSIZE; 799 | } 800 | 801 | acutest_line_indent_(acutest_case_name_[0] ? 3 : 2); 802 | printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title); 803 | 804 | for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) { 805 | size_t line_end = line_beg + BYTES_PER_LINE; 806 | size_t off; 807 | 808 | acutest_line_indent_(acutest_case_name_[0] ? 4 : 3); 809 | printf("%08lx: ", (unsigned long)line_beg); 810 | for(off = line_beg; off < line_end; off++) { 811 | if(off < size) 812 | printf(" %02x", ((const unsigned char*)addr)[off]); 813 | else 814 | printf(" "); 815 | } 816 | 817 | printf(" "); 818 | for(off = line_beg; off < line_end; off++) { 819 | unsigned char byte = ((const unsigned char*)addr)[off]; 820 | if(off < size) 821 | printf("%c", (iscntrl(byte) ? '.' : byte)); 822 | else 823 | break; 824 | } 825 | 826 | printf("\n"); 827 | } 828 | 829 | if(truncate > 0) { 830 | acutest_line_indent_(acutest_case_name_[0] ? 4 : 3); 831 | printf(" ... (and more %u bytes)\n", (unsigned) truncate); 832 | } 833 | } 834 | 835 | /* This is called just before each test */ 836 | static void 837 | acutest_init_(const char *test_name) 838 | { 839 | #ifdef TEST_INIT 840 | TEST_INIT 841 | ; /* Allow for a single unterminated function call */ 842 | #endif 843 | 844 | /* Suppress any warnings about unused variable. */ 845 | (void) test_name; 846 | } 847 | 848 | /* This is called after each test */ 849 | static void 850 | acutest_fini_(const char *test_name) 851 | { 852 | #ifdef TEST_FINI 853 | TEST_FINI 854 | ; /* Allow for a single unterminated function call */ 855 | #endif 856 | 857 | /* Suppress any warnings about unused variable. */ 858 | (void) test_name; 859 | } 860 | 861 | void 862 | acutest_abort_(void) 863 | { 864 | if(acutest_abort_has_jmp_buf_) { 865 | longjmp(acutest_abort_jmp_buf_, 1); 866 | } else { 867 | if(acutest_current_test_ != NULL) 868 | acutest_fini_(acutest_current_test_->name); 869 | abort(); 870 | } 871 | } 872 | 873 | static void 874 | acutest_list_names_(void) 875 | { 876 | const struct acutest_test_* test; 877 | 878 | printf("Unit tests:\n"); 879 | for(test = ´st_list_[0]; test->func != NULL; test++) 880 | printf(" %s\n", test->name); 881 | } 882 | 883 | static void 884 | acutest_remember_(int i) 885 | { 886 | if(acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_) 887 | return; 888 | 889 | acutest_test_data_[i].flags |= ACUTEST_FLAG_RUN_; 890 | acutest_count_++; 891 | } 892 | 893 | static void 894 | acutest_set_success_(int i, int success) 895 | { 896 | acutest_test_data_[i].flags |= success ? ACUTEST_FLAG_SUCCESS_ : ACUTEST_FLAG_FAILURE_; 897 | } 898 | 899 | static void 900 | acutest_set_duration_(int i, double duration) 901 | { 902 | acutest_test_data_[i].duration = duration; 903 | } 904 | 905 | static int 906 | acutest_name_contains_word_(const char* name, const char* pattern) 907 | { 908 | static const char word_delim[] = " \t-_/.,:;"; 909 | const char* substr; 910 | size_t pattern_len; 911 | 912 | pattern_len = strlen(pattern); 913 | 914 | substr = strstr(name, pattern); 915 | while(substr != NULL) { 916 | int starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL); 917 | int ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL); 918 | 919 | if(starts_on_word_boundary && ends_on_word_boundary) 920 | return 1; 921 | 922 | substr = strstr(substr+1, pattern); 923 | } 924 | 925 | return 0; 926 | } 927 | 928 | static int 929 | acutest_lookup_(const char* pattern) 930 | { 931 | int i; 932 | int n = 0; 933 | 934 | /* Try exact match. */ 935 | for(i = 0; i < (int) acutest_list_size_; i++) { 936 | if(strcmp(acutest_list_[i].name, pattern) == 0) { 937 | acutest_remember_(i); 938 | n++; 939 | break; 940 | } 941 | } 942 | if(n > 0) 943 | return n; 944 | 945 | /* Try word match. */ 946 | for(i = 0; i < (int) acutest_list_size_; i++) { 947 | if(acutest_name_contains_word_(acutest_list_[i].name, pattern)) { 948 | acutest_remember_(i); 949 | n++; 950 | } 951 | } 952 | if(n > 0) 953 | return n; 954 | 955 | /* Try relaxed match. */ 956 | for(i = 0; i < (int) acutest_list_size_; i++) { 957 | if(strstr(acutest_list_[i].name, pattern) != NULL) { 958 | acutest_remember_(i); 959 | n++; 960 | } 961 | } 962 | 963 | return n; 964 | } 965 | 966 | 967 | /* Called if anything goes bad in Acutest, or if the unit test ends in other 968 | * way then by normal returning from its function (e.g. exception or some 969 | * abnormal child process termination). */ 970 | static void ACUTEST_ATTRIBUTE_(format (printf, 1, 2)) 971 | acutest_error_(const char* fmt, ...) 972 | { 973 | if(acutest_verbose_level_ == 0) 974 | return; 975 | 976 | if(acutest_verbose_level_ >= 2) { 977 | va_list args; 978 | 979 | acutest_line_indent_(1); 980 | if(acutest_verbose_level_ >= 3) 981 | acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "ERROR: "); 982 | va_start(args, fmt); 983 | vprintf(fmt, args); 984 | va_end(args); 985 | printf("\n"); 986 | } 987 | 988 | if(acutest_verbose_level_ >= 3) { 989 | printf("\n"); 990 | } 991 | } 992 | 993 | /* Call directly the given test unit function. */ 994 | static int 995 | acutest_do_run_(const struct acutest_test_* test, int index) 996 | { 997 | int status = -1; 998 | 999 | acutest_was_aborted_ = 0; 1000 | acutest_current_test_ = test; 1001 | acutest_current_index_ = index; 1002 | acutest_test_failures_ = 0; 1003 | acutest_test_already_logged_ = 0; 1004 | acutest_cond_failed_ = 0; 1005 | 1006 | #ifdef __cplusplus 1007 | try { 1008 | #endif 1009 | acutest_init_(test->name); 1010 | acutest_begin_test_line_(test); 1011 | 1012 | /* This is good to do in case the test unit crashes. */ 1013 | fflush(stdout); 1014 | fflush(stderr); 1015 | 1016 | if(!acutest_worker_) { 1017 | acutest_abort_has_jmp_buf_ = 1; 1018 | if(setjmp(acutest_abort_jmp_buf_) != 0) { 1019 | acutest_was_aborted_ = 1; 1020 | goto aborted; 1021 | } 1022 | } 1023 | 1024 | acutest_timer_get_time_(´st_timer_start_); 1025 | test->func(); 1026 | aborted: 1027 | acutest_abort_has_jmp_buf_ = 0; 1028 | acutest_timer_get_time_(´st_timer_end_); 1029 | 1030 | if(acutest_verbose_level_ >= 3) { 1031 | acutest_line_indent_(1); 1032 | if(acutest_test_failures_ == 0) { 1033 | acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS: "); 1034 | printf("All conditions have passed.\n"); 1035 | 1036 | if(acutest_timer_) { 1037 | acutest_line_indent_(1); 1038 | printf("Duration: "); 1039 | acutest_timer_print_diff_(); 1040 | printf("\n"); 1041 | } 1042 | } else { 1043 | acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: "); 1044 | if(!acutest_was_aborted_) { 1045 | printf("%d condition%s %s failed.\n", 1046 | acutest_test_failures_, 1047 | (acutest_test_failures_ == 1) ? "" : "s", 1048 | (acutest_test_failures_ == 1) ? "has" : "have"); 1049 | } else { 1050 | printf("Aborted.\n"); 1051 | } 1052 | } 1053 | printf("\n"); 1054 | } else if(acutest_verbose_level_ >= 1 && acutest_test_failures_ == 0) { 1055 | acutest_finish_test_line_(0); 1056 | } 1057 | 1058 | status = (acutest_test_failures_ == 0) ? 0 : -1; 1059 | 1060 | #ifdef __cplusplus 1061 | } catch(std::exception& e) { 1062 | const char* what = e.what(); 1063 | acutest_check_(0, NULL, 0, "Threw std::exception"); 1064 | if(what != NULL) 1065 | acutest_message_("std::exception::what(): %s", what); 1066 | 1067 | if(acutest_verbose_level_ >= 3) { 1068 | acutest_line_indent_(1); 1069 | acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: "); 1070 | printf("C++ exception.\n\n"); 1071 | } 1072 | } catch(...) { 1073 | acutest_check_(0, NULL, 0, "Threw an exception"); 1074 | 1075 | if(acutest_verbose_level_ >= 3) { 1076 | acutest_line_indent_(1); 1077 | acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: "); 1078 | printf("C++ exception.\n\n"); 1079 | } 1080 | } 1081 | #endif 1082 | 1083 | acutest_fini_(test->name); 1084 | acutest_case_(NULL); 1085 | acutest_current_test_ = NULL; 1086 | 1087 | return status; 1088 | } 1089 | 1090 | /* Trigger the unit test. If possible (and not suppressed) it starts a child 1091 | * process who calls acutest_do_run_(), otherwise it calls acutest_do_run_() 1092 | * directly. */ 1093 | static void 1094 | acutest_run_(const struct acutest_test_* test, int index, int master_index) 1095 | { 1096 | int failed = 1; 1097 | acutest_timer_type_ start, end; 1098 | 1099 | acutest_current_test_ = test; 1100 | acutest_test_already_logged_ = 0; 1101 | acutest_timer_get_time_(&start); 1102 | 1103 | if(!acutest_no_exec_) { 1104 | 1105 | #if defined(ACUTEST_UNIX_) 1106 | 1107 | pid_t pid; 1108 | int exit_code; 1109 | 1110 | /* Make sure the child starts with empty I/O buffers. */ 1111 | fflush(stdout); 1112 | fflush(stderr); 1113 | 1114 | pid = fork(); 1115 | if(pid == (pid_t)-1) { 1116 | acutest_error_("Cannot fork. %s [%d]", strerror(errno), errno); 1117 | failed = 1; 1118 | } else if(pid == 0) { 1119 | /* Child: Do the test. */ 1120 | acutest_worker_ = 1; 1121 | failed = (acutest_do_run_(test, index) != 0); 1122 | acutest_exit_(failed ? 1 : 0); 1123 | } else { 1124 | /* Parent: Wait until child terminates and analyze its exit code. */ 1125 | waitpid(pid, &exit_code, 0); 1126 | if(WIFEXITED(exit_code)) { 1127 | switch(WEXITSTATUS(exit_code)) { 1128 | case 0: failed = 0; break; /* test has passed. */ 1129 | case 1: /* noop */ break; /* "normal" failure. */ 1130 | default: acutest_error_("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); 1131 | } 1132 | } else if(WIFSIGNALED(exit_code)) { 1133 | char tmp[32]; 1134 | const char* signame; 1135 | switch(WTERMSIG(exit_code)) { 1136 | case SIGINT: signame = "SIGINT"; break; 1137 | case SIGHUP: signame = "SIGHUP"; break; 1138 | case SIGQUIT: signame = "SIGQUIT"; break; 1139 | case SIGABRT: signame = "SIGABRT"; break; 1140 | case SIGKILL: signame = "SIGKILL"; break; 1141 | case SIGSEGV: signame = "SIGSEGV"; break; 1142 | case SIGILL: signame = "SIGILL"; break; 1143 | case SIGTERM: signame = "SIGTERM"; break; 1144 | default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; 1145 | } 1146 | acutest_error_("Test interrupted by %s.", signame); 1147 | } else { 1148 | acutest_error_("Test ended in an unexpected way [%d].", exit_code); 1149 | } 1150 | } 1151 | 1152 | #elif defined(ACUTEST_WIN_) 1153 | 1154 | char buffer[512] = {0}; 1155 | STARTUPINFOA startupInfo; 1156 | PROCESS_INFORMATION processInfo; 1157 | DWORD exitCode; 1158 | 1159 | /* Windows has no fork(). So we propagate all info into the child 1160 | * through a command line arguments. */ 1161 | _snprintf(buffer, sizeof(buffer)-1, 1162 | "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", 1163 | acutest_argv0_, index, acutest_timer_ ? "--time" : "", 1164 | acutest_tap_ ? "--tap" : "", acutest_verbose_level_, 1165 | acutest_colorize_ ? "always" : "never", 1166 | test->name); 1167 | memset(&startupInfo, 0, sizeof(startupInfo)); 1168 | startupInfo.cb = sizeof(STARTUPINFO); 1169 | if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { 1170 | WaitForSingleObject(processInfo.hProcess, INFINITE); 1171 | GetExitCodeProcess(processInfo.hProcess, &exitCode); 1172 | CloseHandle(processInfo.hThread); 1173 | CloseHandle(processInfo.hProcess); 1174 | failed = (exitCode != 0); 1175 | if(exitCode > 1) { 1176 | switch(exitCode) { 1177 | case 3: acutest_error_("Aborted."); break; 1178 | case 0xC0000005: acutest_error_("Access violation."); break; 1179 | default: acutest_error_("Test ended in an unexpected way [%lu].", exitCode); break; 1180 | } 1181 | } 1182 | } else { 1183 | acutest_error_("Cannot create unit test subprocess [%ld].", GetLastError()); 1184 | failed = 1; 1185 | } 1186 | 1187 | #else 1188 | 1189 | /* A platform where we don't know how to run child process. */ 1190 | failed = (acutest_do_run_(test, index) != 0); 1191 | 1192 | #endif 1193 | 1194 | } else { 1195 | /* Child processes suppressed through --no-exec. */ 1196 | failed = (acutest_do_run_(test, index) != 0); 1197 | } 1198 | acutest_timer_get_time_(&end); 1199 | 1200 | acutest_current_test_ = NULL; 1201 | 1202 | acutest_stat_run_units_++; 1203 | if(failed) 1204 | acutest_stat_failed_units_++; 1205 | 1206 | acutest_set_success_(master_index, !failed); 1207 | acutest_set_duration_(master_index, acutest_timer_diff_(start, end)); 1208 | } 1209 | 1210 | #if defined(ACUTEST_WIN_) 1211 | /* Callback for SEH events. */ 1212 | static LONG CALLBACK 1213 | acutest_seh_exception_filter_(EXCEPTION_POINTERS *ptrs) 1214 | { 1215 | acutest_check_(0, NULL, 0, "Unhandled SEH exception"); 1216 | acutest_message_("Exception code: 0x%08lx", ptrs->ExceptionRecord->ExceptionCode); 1217 | acutest_message_("Exception address: 0x%p", ptrs->ExceptionRecord->ExceptionAddress); 1218 | 1219 | fflush(stdout); 1220 | fflush(stderr); 1221 | 1222 | return EXCEPTION_EXECUTE_HANDLER; 1223 | } 1224 | #endif 1225 | 1226 | 1227 | #define ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ 0x0001 1228 | #define ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ 0x0002 1229 | 1230 | #define ACUTEST_CMDLINE_OPTID_NONE_ 0 1231 | #define ACUTEST_CMDLINE_OPTID_UNKNOWN_ (-0x7fffffff + 0) 1232 | #define ACUTEST_CMDLINE_OPTID_MISSINGARG_ (-0x7fffffff + 1) 1233 | #define ACUTEST_CMDLINE_OPTID_BOGUSARG_ (-0x7fffffff + 2) 1234 | 1235 | typedef struct acutest_test_CMDLINE_OPTION_ { 1236 | char shortname; 1237 | const char* longname; 1238 | int id; 1239 | unsigned flags; 1240 | } ACUTEST_CMDLINE_OPTION_; 1241 | 1242 | static int 1243 | acutest_cmdline_handle_short_opt_group_(const ACUTEST_CMDLINE_OPTION_* options, 1244 | const char* arggroup, 1245 | int (*callback)(int /*optval*/, const char* /*arg*/)) 1246 | { 1247 | const ACUTEST_CMDLINE_OPTION_* opt; 1248 | int i; 1249 | int ret = 0; 1250 | 1251 | for(i = 0; arggroup[i] != '\0'; i++) { 1252 | for(opt = options; opt->id != 0; opt++) { 1253 | if(arggroup[i] == opt->shortname) 1254 | break; 1255 | } 1256 | 1257 | if(opt->id != 0 && !(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) { 1258 | ret = callback(opt->id, NULL); 1259 | } else { 1260 | /* Unknown option. */ 1261 | char badoptname[3]; 1262 | badoptname[0] = '-'; 1263 | badoptname[1] = arggroup[i]; 1264 | badoptname[2] = '\0'; 1265 | ret = callback((opt->id != 0 ? ACUTEST_CMDLINE_OPTID_MISSINGARG_ : ACUTEST_CMDLINE_OPTID_UNKNOWN_), 1266 | badoptname); 1267 | } 1268 | 1269 | if(ret != 0) 1270 | break; 1271 | } 1272 | 1273 | return ret; 1274 | } 1275 | 1276 | #define ACUTEST_CMDLINE_AUXBUF_SIZE_ 32 1277 | 1278 | static int 1279 | acutest_cmdline_read_(const ACUTEST_CMDLINE_OPTION_* options, int argc, char** argv, 1280 | int (*callback)(int /*optval*/, const char* /*arg*/)) 1281 | { 1282 | 1283 | const ACUTEST_CMDLINE_OPTION_* opt; 1284 | char auxbuf[ACUTEST_CMDLINE_AUXBUF_SIZE_+1]; 1285 | int after_doubledash = 0; 1286 | int i = 1; 1287 | int ret = 0; 1288 | 1289 | auxbuf[ACUTEST_CMDLINE_AUXBUF_SIZE_] = '\0'; 1290 | 1291 | while(i < argc) { 1292 | if(after_doubledash || strcmp(argv[i], "-") == 0) { 1293 | /* Non-option argument. */ 1294 | ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]); 1295 | } else if(strcmp(argv[i], "--") == 0) { 1296 | /* End of options. All the remaining members are non-option arguments. */ 1297 | after_doubledash = 1; 1298 | } else if(argv[i][0] != '-') { 1299 | /* Non-option argument. */ 1300 | ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]); 1301 | } else { 1302 | for(opt = options; opt->id != 0; opt++) { 1303 | if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { 1304 | size_t len = strlen(opt->longname); 1305 | if(strncmp(argv[i]+2, opt->longname, len) == 0) { 1306 | /* Regular long option. */ 1307 | if(argv[i][2+len] == '\0') { 1308 | /* with no argument provided. */ 1309 | if(!(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) 1310 | ret = callback(opt->id, NULL); 1311 | else 1312 | ret = callback(ACUTEST_CMDLINE_OPTID_MISSINGARG_, argv[i]); 1313 | break; 1314 | } else if(argv[i][2+len] == '=') { 1315 | /* with an argument provided. */ 1316 | if(opt->flags & (ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ | ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) { 1317 | ret = callback(opt->id, argv[i]+2+len+1); 1318 | } else { 1319 | sprintf(auxbuf, "--%s", opt->longname); 1320 | ret = callback(ACUTEST_CMDLINE_OPTID_BOGUSARG_, auxbuf); 1321 | } 1322 | break; 1323 | } else { 1324 | continue; 1325 | } 1326 | } 1327 | } else if(opt->shortname != '\0' && argv[i][0] == '-') { 1328 | if(argv[i][1] == opt->shortname) { 1329 | /* Regular short option. */ 1330 | if(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_) { 1331 | if(argv[i][2] != '\0') 1332 | ret = callback(opt->id, argv[i]+2); 1333 | else if(i+1 < argc) 1334 | ret = callback(opt->id, argv[++i]); 1335 | else 1336 | ret = callback(ACUTEST_CMDLINE_OPTID_MISSINGARG_, argv[i]); 1337 | break; 1338 | } else { 1339 | ret = callback(opt->id, NULL); 1340 | 1341 | /* There might be more (argument-less) short options 1342 | * grouped together. */ 1343 | if(ret == 0 && argv[i][2] != '\0') 1344 | ret = acutest_cmdline_handle_short_opt_group_(options, argv[i]+2, callback); 1345 | break; 1346 | } 1347 | } 1348 | } 1349 | } 1350 | 1351 | if(opt->id == 0) { /* still not handled? */ 1352 | if(argv[i][0] != '-') { 1353 | /* Non-option argument. */ 1354 | ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]); 1355 | } else { 1356 | /* Unknown option. */ 1357 | char* badoptname = argv[i]; 1358 | 1359 | if(strncmp(badoptname, "--", 2) == 0) { 1360 | /* Strip any argument from the long option. */ 1361 | char* assignment = strchr(badoptname, '='); 1362 | if(assignment != NULL) { 1363 | size_t len = assignment - badoptname; 1364 | if(len > ACUTEST_CMDLINE_AUXBUF_SIZE_) 1365 | len = ACUTEST_CMDLINE_AUXBUF_SIZE_; 1366 | strncpy(auxbuf, badoptname, len); 1367 | auxbuf[len] = '\0'; 1368 | badoptname = auxbuf; 1369 | } 1370 | } 1371 | 1372 | ret = callback(ACUTEST_CMDLINE_OPTID_UNKNOWN_, badoptname); 1373 | } 1374 | } 1375 | } 1376 | 1377 | if(ret != 0) 1378 | return ret; 1379 | i++; 1380 | } 1381 | 1382 | return ret; 1383 | } 1384 | 1385 | static void 1386 | acutest_help_(void) 1387 | { 1388 | printf("Usage: %s [options] [test...]\n", acutest_argv0_); 1389 | printf("\n"); 1390 | printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); 1391 | printf("tests in the suite but those listed. By default, if no tests are specified\n"); 1392 | printf("on the command line, all unit tests in the suite are run.\n"); 1393 | printf("\n"); 1394 | printf("Options:\n"); 1395 | printf(" -s, --skip Execute all unit tests but the listed ones\n"); 1396 | printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); 1397 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1398 | printf(" -E, --no-exec Same as --exec=never\n"); 1399 | #if defined ACUTEST_WIN_ 1400 | printf(" -t, --time Measure test duration\n"); 1401 | #elif defined ACUTEST_HAS_POSIX_TIMER_ 1402 | printf(" -t, --time Measure test duration (real time)\n"); 1403 | printf(" --time=TIMER Measure test duration, using given timer\n"); 1404 | printf(" (TIMER is one of 'real', 'cpu')\n"); 1405 | #endif 1406 | printf(" --no-summary Suppress printing of test results summary\n"); 1407 | printf(" --tap Produce TAP-compliant output\n"); 1408 | printf(" (See https://testanything.org/)\n"); 1409 | printf(" -x, --xml-output=FILE Enable XUnit output to the given file\n"); 1410 | printf(" -l, --list List unit tests in the suite and exit\n"); 1411 | printf(" -v, --verbose Make output more verbose\n"); 1412 | printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); 1413 | printf(" 0 ... Be silent\n"); 1414 | printf(" 1 ... Output one line per test (and summary)\n"); 1415 | printf(" 2 ... As 1 and failed conditions (this is default)\n"); 1416 | printf(" 3 ... As 1 and all conditions (and extended summary)\n"); 1417 | printf(" -q, --quiet Same as --verbose=0\n"); 1418 | printf(" --color[=WHEN] Enable colorized output\n"); 1419 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1420 | printf(" --no-color Same as --color=never\n"); 1421 | printf(" -h, --help Display this help and exit\n"); 1422 | 1423 | if(acutest_list_size_ < 16) { 1424 | printf("\n"); 1425 | acutest_list_names_(); 1426 | } 1427 | } 1428 | 1429 | static const ACUTEST_CMDLINE_OPTION_ acutest_cmdline_options_[] = { 1430 | { 's', "skip", 's', 0 }, 1431 | { 0, "exec", 'e', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, 1432 | { 'E', "no-exec", 'E', 0 }, 1433 | #if defined ACUTEST_WIN_ 1434 | { 't', "time", 't', 0 }, 1435 | { 0, "timer", 't', 0 }, /* kept for compatibility */ 1436 | #elif defined ACUTEST_HAS_POSIX_TIMER_ 1437 | { 't', "time", 't', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, 1438 | { 0, "timer", 't', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, /* kept for compatibility */ 1439 | #endif 1440 | { 0, "no-summary", 'S', 0 }, 1441 | { 0, "tap", 'T', 0 }, 1442 | { 'l', "list", 'l', 0 }, 1443 | { 'v', "verbose", 'v', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, 1444 | { 'q', "quiet", 'q', 0 }, 1445 | { 0, "color", 'c', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, 1446 | { 0, "no-color", 'C', 0 }, 1447 | { 'h', "help", 'h', 0 }, 1448 | { 0, "worker", 'w', ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ }, /* internal */ 1449 | { 'x', "xml-output", 'x', ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ }, 1450 | { 0, NULL, 0, 0 } 1451 | }; 1452 | 1453 | static int 1454 | acutest_cmdline_callback_(int id, const char* arg) 1455 | { 1456 | switch(id) { 1457 | case 's': 1458 | acutest_skip_mode_ = 1; 1459 | break; 1460 | 1461 | case 'e': 1462 | if(arg == NULL || strcmp(arg, "always") == 0) { 1463 | acutest_no_exec_ = 0; 1464 | } else if(strcmp(arg, "never") == 0) { 1465 | acutest_no_exec_ = 1; 1466 | } else if(strcmp(arg, "auto") == 0) { 1467 | /*noop*/ 1468 | } else { 1469 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", acutest_argv0_, arg); 1470 | fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_); 1471 | acutest_exit_(2); 1472 | } 1473 | break; 1474 | 1475 | case 'E': 1476 | acutest_no_exec_ = 1; 1477 | break; 1478 | 1479 | case 't': 1480 | #if defined ACUTEST_WIN_ || defined ACUTEST_HAS_POSIX_TIMER_ 1481 | if(arg == NULL || strcmp(arg, "real") == 0) { 1482 | acutest_timer_ = 1; 1483 | #ifndef ACUTEST_WIN_ 1484 | } else if(strcmp(arg, "cpu") == 0) { 1485 | acutest_timer_ = 2; 1486 | #endif 1487 | } else { 1488 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --time.\n", acutest_argv0_, arg); 1489 | fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_); 1490 | acutest_exit_(2); 1491 | } 1492 | #endif 1493 | break; 1494 | 1495 | case 'S': 1496 | acutest_no_summary_ = 1; 1497 | break; 1498 | 1499 | case 'T': 1500 | acutest_tap_ = 1; 1501 | break; 1502 | 1503 | case 'l': 1504 | acutest_list_names_(); 1505 | acutest_exit_(0); 1506 | break; 1507 | 1508 | case 'v': 1509 | acutest_verbose_level_ = (arg != NULL ? atoi(arg) : acutest_verbose_level_+1); 1510 | break; 1511 | 1512 | case 'q': 1513 | acutest_verbose_level_ = 0; 1514 | break; 1515 | 1516 | case 'c': 1517 | if(arg == NULL || strcmp(arg, "always") == 0) { 1518 | acutest_colorize_ = 1; 1519 | } else if(strcmp(arg, "never") == 0) { 1520 | acutest_colorize_ = 0; 1521 | } else if(strcmp(arg, "auto") == 0) { 1522 | /*noop*/ 1523 | } else { 1524 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", acutest_argv0_, arg); 1525 | fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_); 1526 | acutest_exit_(2); 1527 | } 1528 | break; 1529 | 1530 | case 'C': 1531 | acutest_colorize_ = 0; 1532 | break; 1533 | 1534 | case 'h': 1535 | acutest_help_(); 1536 | acutest_exit_(0); 1537 | break; 1538 | 1539 | case 'w': 1540 | acutest_worker_ = 1; 1541 | acutest_worker_index_ = atoi(arg); 1542 | break; 1543 | case 'x': 1544 | acutest_xml_output_ = fopen(arg, "w"); 1545 | if (!acutest_xml_output_) { 1546 | fprintf(stderr, "Unable to open '%s': %s\n", arg, strerror(errno)); 1547 | acutest_exit_(2); 1548 | } 1549 | break; 1550 | 1551 | case 0: 1552 | if(acutest_lookup_(arg) == 0) { 1553 | fprintf(stderr, "%s: Unrecognized unit test '%s'\n", acutest_argv0_, arg); 1554 | fprintf(stderr, "Try '%s --list' for list of unit tests.\n", acutest_argv0_); 1555 | acutest_exit_(2); 1556 | } 1557 | break; 1558 | 1559 | case ACUTEST_CMDLINE_OPTID_UNKNOWN_: 1560 | fprintf(stderr, "Unrecognized command line option '%s'.\n", arg); 1561 | fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_); 1562 | acutest_exit_(2); 1563 | break; 1564 | 1565 | case ACUTEST_CMDLINE_OPTID_MISSINGARG_: 1566 | fprintf(stderr, "The command line option '%s' requires an argument.\n", arg); 1567 | fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_); 1568 | acutest_exit_(2); 1569 | break; 1570 | 1571 | case ACUTEST_CMDLINE_OPTID_BOGUSARG_: 1572 | fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg); 1573 | fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_); 1574 | acutest_exit_(2); 1575 | break; 1576 | } 1577 | 1578 | return 0; 1579 | } 1580 | 1581 | 1582 | #ifdef ACUTEST_LINUX_ 1583 | static int 1584 | acutest_is_tracer_present_(void) 1585 | { 1586 | /* Must be large enough so the line 'TracerPid: ${PID}' can fit in. */ 1587 | static const int OVERLAP = 32; 1588 | 1589 | char buf[256+OVERLAP+1]; 1590 | int tracer_present = 0; 1591 | int fd; 1592 | size_t n_read = 0; 1593 | 1594 | fd = open("/proc/self/status", O_RDONLY); 1595 | if(fd == -1) 1596 | return 0; 1597 | 1598 | while(1) { 1599 | static const char pattern[] = "TracerPid:"; 1600 | const char* field; 1601 | 1602 | while(n_read < sizeof(buf) - 1) { 1603 | ssize_t n; 1604 | 1605 | n = read(fd, buf + n_read, sizeof(buf) - 1 - n_read); 1606 | if(n <= 0) 1607 | break; 1608 | n_read += n; 1609 | } 1610 | buf[n_read] = '\0'; 1611 | 1612 | field = strstr(buf, pattern); 1613 | if(field != NULL && field < buf + sizeof(buf) - OVERLAP) { 1614 | pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); 1615 | tracer_present = (tracer_pid != 0); 1616 | break; 1617 | } 1618 | 1619 | if(n_read == sizeof(buf)-1) { 1620 | memmove(buf, buf + sizeof(buf)-1 - OVERLAP, OVERLAP); 1621 | n_read = OVERLAP; 1622 | } else { 1623 | break; 1624 | } 1625 | } 1626 | 1627 | close(fd); 1628 | return tracer_present; 1629 | } 1630 | #endif 1631 | 1632 | int 1633 | main(int argc, char** argv) 1634 | { 1635 | int i; 1636 | 1637 | acutest_argv0_ = argv[0]; 1638 | 1639 | #if defined ACUTEST_UNIX_ 1640 | acutest_colorize_ = isatty(STDOUT_FILENO); 1641 | #elif defined ACUTEST_WIN_ 1642 | #if defined _BORLANDC_ 1643 | acutest_colorize_ = isatty(_fileno(stdout)); 1644 | #else 1645 | acutest_colorize_ = _isatty(_fileno(stdout)); 1646 | #endif 1647 | #else 1648 | acutest_colorize_ = 0; 1649 | #endif 1650 | 1651 | /* Count all test units */ 1652 | acutest_list_size_ = 0; 1653 | for(i = 0; acutest_list_[i].func != NULL; i++) 1654 | acutest_list_size_++; 1655 | 1656 | acutest_test_data_ = (struct acutest_test_data_*)calloc(acutest_list_size_, sizeof(struct acutest_test_data_)); 1657 | if(acutest_test_data_ == NULL) { 1658 | fprintf(stderr, "Out of memory.\n"); 1659 | acutest_exit_(2); 1660 | } 1661 | 1662 | /* Parse options */ 1663 | acutest_cmdline_read_(acutest_cmdline_options_, argc, argv, acutest_cmdline_callback_); 1664 | 1665 | /* Initialize the proper timer. */ 1666 | acutest_timer_init_(); 1667 | 1668 | #if defined(ACUTEST_WIN_) 1669 | SetUnhandledExceptionFilter(acutest_seh_exception_filter_); 1670 | #ifdef _MSC_VER 1671 | _set_abort_behavior(0, _WRITE_ABORT_MSG); 1672 | #endif 1673 | #endif 1674 | 1675 | /* By default, we want to run all tests. */ 1676 | if(acutest_count_ == 0) { 1677 | for(i = 0; acutest_list_[i].func != NULL; i++) 1678 | acutest_remember_(i); 1679 | } 1680 | 1681 | /* Guess whether we want to run unit tests as child processes. */ 1682 | if(acutest_no_exec_ < 0) { 1683 | acutest_no_exec_ = 0; 1684 | 1685 | if(acutest_count_ <= 1) { 1686 | acutest_no_exec_ = 1; 1687 | } else { 1688 | #ifdef ACUTEST_WIN_ 1689 | if(IsDebuggerPresent()) 1690 | acutest_no_exec_ = 1; 1691 | #endif 1692 | #ifdef ACUTEST_LINUX_ 1693 | if(acutest_is_tracer_present_()) 1694 | acutest_no_exec_ = 1; 1695 | #endif 1696 | #ifdef RUNNING_ON_VALGRIND 1697 | /* RUNNING_ON_VALGRIND is provided by optionally included */ 1698 | if(RUNNING_ON_VALGRIND) 1699 | acutest_no_exec_ = 1; 1700 | #endif 1701 | } 1702 | } 1703 | 1704 | if(acutest_tap_) { 1705 | /* TAP requires we know test result ("ok", "not ok") before we output 1706 | * anything about the test, and this gets problematic for larger verbose 1707 | * levels. */ 1708 | if(acutest_verbose_level_ > 2) 1709 | acutest_verbose_level_ = 2; 1710 | 1711 | /* TAP harness should provide some summary. */ 1712 | acutest_no_summary_ = 1; 1713 | 1714 | if(!acutest_worker_) 1715 | printf("1..%d\n", (int) acutest_count_); 1716 | } 1717 | 1718 | int index = acutest_worker_index_; 1719 | for(i = 0; acutest_list_[i].func != NULL; i++) { 1720 | int run = (acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_); 1721 | if (acutest_skip_mode_) /* Run all tests except those listed. */ 1722 | run = !run; 1723 | if(run) 1724 | acutest_run_(´st_list_[i], index++, i); 1725 | } 1726 | 1727 | /* Write a summary */ 1728 | if(!acutest_no_summary_ && acutest_verbose_level_ >= 1) { 1729 | if(acutest_verbose_level_ >= 3) { 1730 | acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Summary:\n"); 1731 | 1732 | printf(" Count of all unit tests: %4d\n", (int) acutest_list_size_); 1733 | printf(" Count of run unit tests: %4d\n", acutest_stat_run_units_); 1734 | printf(" Count of failed unit tests: %4d\n", acutest_stat_failed_units_); 1735 | printf(" Count of skipped unit tests: %4d\n", (int) acutest_list_size_ - acutest_stat_run_units_); 1736 | } 1737 | 1738 | if(acutest_stat_failed_units_ == 0) { 1739 | acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS:"); 1740 | printf(" All unit tests have passed.\n"); 1741 | } else { 1742 | acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED:"); 1743 | printf(" %d of %d unit tests %s failed.\n", 1744 | acutest_stat_failed_units_, acutest_stat_run_units_, 1745 | (acutest_stat_failed_units_ == 1) ? "has" : "have"); 1746 | } 1747 | 1748 | if(acutest_verbose_level_ >= 3) 1749 | printf("\n"); 1750 | } 1751 | 1752 | if (acutest_xml_output_) { 1753 | #if defined ACUTEST_UNIX_ 1754 | char *suite_name = basename(argv[0]); 1755 | #elif defined ACUTEST_WIN_ 1756 | char suite_name[_MAX_FNAME]; 1757 | _splitpath(argv[0], NULL, NULL, suite_name, NULL); 1758 | #else 1759 | const char *suite_name = argv[0]; 1760 | #endif 1761 | fprintf(acutest_xml_output_, "\n"); 1762 | fprintf(acutest_xml_output_, "\n", 1763 | suite_name, (int)acutest_list_size_, acutest_stat_failed_units_, acutest_stat_failed_units_, 1764 | (int)acutest_list_size_ - acutest_stat_run_units_); 1765 | for(i = 0; acutest_list_[i].func != NULL; i++) { 1766 | struct acutest_test_data_ *details = ´st_test_data_[i]; 1767 | fprintf(acutest_xml_output_, " \n", acutest_list_[i].name, details->duration); 1768 | if (details->flags & ACUTEST_FLAG_FAILURE_) 1769 | fprintf(acutest_xml_output_, " \n"); 1770 | if (!(details->flags & ACUTEST_FLAG_FAILURE_) && !(details->flags & ACUTEST_FLAG_SUCCESS_)) 1771 | fprintf(acutest_xml_output_, " \n"); 1772 | fprintf(acutest_xml_output_, " \n"); 1773 | } 1774 | fprintf(acutest_xml_output_, "\n"); 1775 | fclose(acutest_xml_output_); 1776 | } 1777 | 1778 | acutest_cleanup_(); 1779 | 1780 | return (acutest_stat_failed_units_ == 0) ? 0 : 1; 1781 | } 1782 | 1783 | 1784 | #endif /* #ifndef TEST_NO_MAIN */ 1785 | 1786 | #ifdef _MSC_VER 1787 | #pragma warning(pop) 1788 | #endif 1789 | 1790 | #ifdef __cplusplus 1791 | } /* extern "C" */ 1792 | #endif 1793 | 1794 | #endif /* #ifndef ACUTEST_H */ 1795 | -------------------------------------------------------------------------------- /tests/embedded_cli_fuzzer.c: -------------------------------------------------------------------------------- 1 | #include "embedded_cli.c" 2 | 3 | int LLVMFuzzerTestOneInput(const char *data, int size) 4 | { 5 | struct embedded_cli cli; 6 | 7 | embedded_cli_init(&cli, NULL, NULL, NULL); 8 | 9 | for (int i = 0; i < size; i++) 10 | embedded_cli_insert_char(&cli, data[i]); 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/embedded_cli_test.c: -------------------------------------------------------------------------------- 1 | #include "acutest.h" 2 | #include "embedded_cli.h" 3 | 4 | // Some ANSI escape sequences 5 | 6 | #define CSI "\x1b[" 7 | #define UP CSI "1A" 8 | #define DOWN CSI "1B" 9 | #define RIGHT CSI "1C" 10 | #define LEFT CSI "1D" 11 | #define HOME CSI "H" 12 | #define END CSI "F" 13 | #define DELETE CSI "3~" 14 | #define CTRL_A "\x01" 15 | #define CTRL_C "\x03" 16 | #define CTRL_E "\x05" 17 | #define CTRL_K "\x0b" 18 | #define CTRL_L "\x0c" 19 | #define CTRL_R "\x12" 20 | 21 | static void cli_equals(const struct embedded_cli *cli, const char *line) 22 | { 23 | const char *cli_line = embedded_cli_get_line(cli); 24 | TEST_ASSERT_(cli_line != NULL, "No line available"); 25 | TEST_ASSERT_(strcmp(cli_line, line) == 0, "Expected line '%s' got '%s'", 26 | line, cli_line); 27 | } 28 | 29 | static void test_insert_line(struct embedded_cli *cli, const char *line) 30 | { 31 | for (; line && *line; line++) 32 | embedded_cli_insert_char(cli, *line); 33 | } 34 | 35 | static void test_simple(void) 36 | { 37 | struct embedded_cli cli; 38 | embedded_cli_init(&cli, NULL, NULL, NULL); 39 | test_insert_line(&cli, "a\n"); 40 | cli_equals(&cli, "a"); 41 | } 42 | 43 | static void test_argc(void) 44 | { 45 | struct embedded_cli cli; 46 | char **argv; 47 | embedded_cli_init(&cli, NULL, NULL, NULL); 48 | test_insert_line(&cli, " foo \t blah blarg \n"); 49 | TEST_ASSERT(embedded_cli_argc(&cli, &argv) == 3); 50 | TEST_ASSERT(strcmp(argv[0], "foo") == 0); 51 | TEST_ASSERT(strcmp(argv[1], "blah") == 0); 52 | TEST_ASSERT(strcmp(argv[2], "blarg") == 0); 53 | } 54 | 55 | static void test_delete(void) 56 | { 57 | struct embedded_cli cli; 58 | embedded_cli_init(&cli, NULL, NULL, NULL); 59 | test_insert_line(&cli, "a\bb\n"); 60 | cli_equals(&cli, "b"); 61 | } 62 | 63 | static void test_cursor_left(void) 64 | { 65 | struct embedded_cli cli; 66 | embedded_cli_init(&cli, NULL, NULL, NULL); 67 | test_insert_line(&cli, "AB" LEFT "C\n"); 68 | cli_equals(&cli, "ACB"); 69 | 70 | embedded_cli_init(&cli, NULL, NULL, NULL); 71 | test_insert_line(&cli, "AB" LEFT LEFT "C\n"); 72 | cli_equals(&cli, "CAB"); 73 | 74 | embedded_cli_init(&cli, NULL, NULL, NULL); 75 | test_insert_line(&cli, "AB" LEFT LEFT "C\n"); 76 | cli_equals(&cli, "CAB"); 77 | } 78 | 79 | static void test_cursor_right(void) 80 | { 81 | struct embedded_cli cli; 82 | embedded_cli_init(&cli, NULL, NULL, NULL); 83 | test_insert_line(&cli, "AB" LEFT LEFT RIGHT "C\n"); 84 | cli_equals(&cli, "ACB"); 85 | } 86 | 87 | #if EMBEDDED_CLI_HISTORY_LEN 88 | static void test_history(void) 89 | { 90 | struct embedded_cli cli; 91 | const char *line; 92 | embedded_cli_init(&cli, NULL, NULL, NULL); 93 | test_insert_line(&cli, "First\n"); 94 | test_insert_line(&cli, "Second\n"); 95 | test_insert_line(&cli, "Third\n"); 96 | line = embedded_cli_get_history(&cli, 0); 97 | TEST_ASSERT(line && strcmp(line, "Third") == 0); 98 | line = embedded_cli_get_history(&cli, 1); 99 | TEST_ASSERT(line && strcmp(line, "Second") == 0); 100 | line = embedded_cli_get_history(&cli, 2); 101 | TEST_ASSERT(line && strcmp(line, "First") == 0); 102 | } 103 | 104 | static void test_history_keys(void) 105 | { 106 | struct embedded_cli cli; 107 | embedded_cli_init(&cli, NULL, NULL, NULL); 108 | test_insert_line(&cli, "First\n"); 109 | test_insert_line(&cli, "Second\n"); 110 | test_insert_line(&cli, "Third\n"); 111 | test_insert_line(&cli, UP UP UP DOWN "\n"); 112 | cli_equals(&cli, "Second"); 113 | } 114 | 115 | static void test_search(void) 116 | { 117 | struct embedded_cli cli; 118 | embedded_cli_init(&cli, NULL, NULL, NULL); 119 | test_insert_line(&cli, "First\n"); 120 | test_insert_line(&cli, "Second\n"); 121 | test_insert_line(&cli, "Third\n"); 122 | test_insert_line(&cli, CTRL_R "Se\n"); 123 | cli_equals(&cli, "Second"); 124 | } 125 | 126 | static char output[1024]; 127 | 128 | // Super minimal tty code interpreter so we can work out what 129 | // the user's display looks like 130 | static void output_putchar(void *data, char ch, bool is_last) 131 | { 132 | static int output_pos = 0; 133 | static bool have_escape = false; 134 | static bool have_csi = false; 135 | (void)is_last; 136 | (void)data; 137 | if (ch == '\x1b') { 138 | have_escape = true; 139 | have_csi = false; 140 | return; 141 | } else if (have_escape && ch == '[') { 142 | have_csi = true; 143 | return; 144 | } 145 | 146 | if (have_csi) { 147 | // just ignore them 148 | if (ch >= 'A' && ch <= 'Z') { 149 | if (ch == 'K') // CLEAR_EOL 150 | memset(&output[output_pos], 0, sizeof(output) - output_pos); 151 | have_csi = false; 152 | } 153 | } else { 154 | if (ch == '\b') { 155 | output_pos = output_pos > 0 ? output_pos - 1 : 0; 156 | } else if (ch == '\n') { 157 | output_pos = 0; 158 | memset(output, 0, sizeof(output)); 159 | } else { 160 | output[output_pos++] = ch; 161 | // output[output_pos] = '\0'; 162 | } 163 | } 164 | } 165 | 166 | #define UP_ARROW "\x1b[A" 167 | #define DOWN_ARROW "\x1b[B" 168 | 169 | static void test_up_down(void) 170 | { 171 | struct embedded_cli cli; 172 | embedded_cli_init(&cli, "prompt> ", output_putchar, NULL); 173 | embedded_cli_prompt(&cli); 174 | TEST_ASSERT(strcmp(output, "prompt> ") == 0); 175 | test_insert_line(&cli, "cmd 1\n"); 176 | test_insert_line(&cli, "cmd 2\n"); 177 | test_insert_line(&cli, "cmd 3\n"); 178 | test_insert_line(&cli, "cmd 4\n"); 179 | embedded_cli_prompt(&cli); 180 | test_insert_line(&cli, UP_ARROW); 181 | TEST_ASSERT(strcmp(output, "prompt> cmd 4") == 0); 182 | test_insert_line(&cli, UP_ARROW); 183 | TEST_ASSERT(strcmp(output, "prompt> cmd 3") == 0); 184 | test_insert_line(&cli, DOWN_ARROW); 185 | test_insert_line(&cli, DOWN_ARROW); 186 | TEST_ASSERT(strcmp(output, "prompt> ") == 0); 187 | } 188 | #endif 189 | 190 | /** 191 | * The above tests are all quite specific. This test is where we can put any 192 | * other random ideas/corner cases 193 | */ 194 | static void test_multiple(void) 195 | { 196 | struct { 197 | const char *input; 198 | const char *output; 199 | } test_cases[] = { 200 | {"abc" LEFT LEFT "\b\n", "bc"}, 201 | {"abc\b\b\b\b\b\b\b\b\n", ""}, 202 | {"abc\b\b\b\bc\n", "c"}, 203 | {LEFT LEFT RIGHT RIGHT "a" LEFT RIGHT "\n", "a"}, 204 | #if EMBEDDED_CLI_HISTORY_LEN 205 | {UP UP "\n", "c"}, 206 | {"foo" UP "\n", "c"}, 207 | #endif 208 | {"abc" CTRL_C "xyz\n", "xyz"}, 209 | {"abc" CTRL_A "def\n", "defabc"}, 210 | {"abc" CTRL_A "d" CTRL_E "fg\n", "dabcfg"}, 211 | {"abc" HOME "def\n", "defabc"}, 212 | {"abc" HOME "d" END "fg\n", "dabcfg"}, 213 | {"abc" HOME DELETE DELETE "\n", "c"}, 214 | {"abc" LEFT LEFT CTRL_K "\n", "a"}, 215 | {"abc" LEFT LEFT CTRL_L "\n", "abc"}, 216 | {NULL, NULL}, 217 | }; 218 | 219 | struct embedded_cli cli; 220 | embedded_cli_init(&cli, NULL, NULL, NULL); 221 | for (int i = 0; test_cases[i].input; i++) { 222 | test_insert_line(&cli, test_cases[i].input); 223 | cli_equals(&cli, test_cases[i].output); 224 | } 225 | } 226 | 227 | #define MAX_OUTPUT_LEN 50 228 | 229 | static void callback(void *data, char ch, bool is_last) 230 | { 231 | (void)is_last; 232 | char *d = (char *)data; 233 | if (d) { 234 | int len = strlen(d); 235 | if (len < MAX_OUTPUT_LEN - 1) { 236 | d[len] = ch; 237 | d[len + 1] = '\0'; 238 | } 239 | } 240 | } 241 | 242 | static void test_echo(void) 243 | { 244 | struct embedded_cli cli; 245 | char output[MAX_OUTPUT_LEN] = "\0"; 246 | embedded_cli_init(&cli, "prompt> ", callback, output); 247 | embedded_cli_prompt(&cli); 248 | test_insert_line(&cli, "foo\n"); 249 | TEST_ASSERT(strcmp(output, "prompt> foo\r\n") == 0); 250 | } 251 | 252 | static void test_quotes(void) 253 | { 254 | struct embedded_cli cli; 255 | char **argv; 256 | embedded_cli_init(&cli, NULL, NULL, NULL); 257 | test_insert_line(&cli, "this 'is some' \"text with\" '\"quotes\"' " 258 | "'concat'enated \\\"escape\\\" \n"); 259 | TEST_ASSERT(embedded_cli_argc(&cli, &argv) == 6); 260 | TEST_ASSERT(strcmp(argv[0], "this") == 0); 261 | TEST_ASSERT(strcmp(argv[1], "is some") == 0); 262 | TEST_ASSERT(strcmp(argv[2], "text with") == 0); 263 | TEST_ASSERT(strcmp(argv[3], "\"quotes\"") == 0); 264 | TEST_ASSERT(strcmp(argv[4], "concatenated") == 0); 265 | TEST_ASSERT(strcmp(argv[5], "\"escape\"") == 0); 266 | } 267 | 268 | static void test_too_many_args(void) 269 | { 270 | struct embedded_cli cli; 271 | char **argv; 272 | embedded_cli_init(&cli, NULL, NULL, NULL); 273 | test_insert_line(&cli, "a b c d e f g h i j k l m n o p q r s\n"); 274 | TEST_ASSERT(embedded_cli_argc(&cli, &argv) == EMBEDDED_CLI_MAX_ARGC - 1); 275 | TEST_ASSERT(strcmp(argv[0], "a") == 0); 276 | TEST_ASSERT(strcmp(argv[1], "b") == 0); 277 | TEST_ASSERT(strcmp(argv[13], "n") == 0); 278 | TEST_ASSERT(strcmp(argv[14], "o") == 0); 279 | TEST_ASSERT(argv[15] == NULL); 280 | } 281 | 282 | static void test_max_chars(void) 283 | { 284 | struct embedded_cli cli; 285 | embedded_cli_init(&cli, NULL, NULL, NULL); 286 | // Fill in the buffer 287 | for (int i = 0; i < EMBEDDED_CLI_MAX_LINE; i++) { 288 | embedded_cli_insert_char(&cli, 'b'); 289 | } 290 | // Make sure we cannot insert a character now 291 | embedded_cli_insert_char(&cli, 'x'); 292 | TEST_ASSERT(cli.buffer[sizeof(cli.buffer) - 2] == 'b'); 293 | TEST_ASSERT(cli.buffer[sizeof(cli.buffer) - 1] == '\0'); 294 | // Make sure we can backspace & change the last character 295 | embedded_cli_insert_char(&cli, '\b'); 296 | embedded_cli_insert_char(&cli, 'f'); 297 | // There is always a nul at the end, so the one before that should now be 298 | // an f 299 | TEST_ASSERT(cli.buffer[sizeof(cli.buffer) - 2] == 'f'); 300 | } 301 | 302 | TEST_LIST = { 303 | {"simple", test_simple}, 304 | {"argc", test_argc}, 305 | {"delete", test_delete}, 306 | {"cursor_left", test_cursor_left}, 307 | {"cursor_right", test_cursor_right}, 308 | #if EMBEDDED_CLI_HISTORY_LEN 309 | {"history", test_history}, 310 | {"history_keys", test_history_keys}, 311 | {"search", test_search}, 312 | {"up_down", test_up_down}, 313 | #endif 314 | {"multiple", test_multiple}, 315 | {"echo", test_echo}, 316 | {"quotes", test_quotes}, 317 | {"too_many_args", test_too_many_args}, 318 | {"max_chars", test_max_chars}, 319 | {NULL, NULL}, 320 | }; --------------------------------------------------------------------------------