├── .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 |
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 | [](https://github.com/AndreRenaud/EmbeddedCLI/actions)
8 |
9 | [](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 | };
--------------------------------------------------------------------------------