dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 95 / 8 / 103
Functions: 100.0% 4 / 0 / 4
Branches: 83.9% 52 / 24 / 86

src/command/run.c
Line Branch Exec Source
1 #include "run.h"
2 #include "args.h"
3 #include "parse.h"
4 #include "change.h"
5 #include "options.h"
6 #include "util/debug.h"
7 #include "util/ptr-array.h"
8 #include "util/xmalloc.h"
9
10 enum {
11 MAX_RECURSION_DEPTH = 16, // Maximum number of `alias` expansions
12 };
13
14 static bool run_commands(CommandRunner *runner, const PointerArray *array);
15
16 // Recursion is limited by MAX_RECURSION_DEPTH
17 // NOLINTNEXTLINE(misc-no-recursion)
18 8049 static bool run_command(CommandRunner *runner, char **av)
19 {
20 8049 const CommandSet *cmds = runner->cmds;
21 8049 struct EditorState *e = runner->e;
22 8049 ErrorBuffer *ebuf = runner->ebuf;
23 8049 BUG_ON(!cmds);
24 8049 BUG_ON(!e);
25 8049 BUG_ON(!ebuf);
26
27 8049 const Command *cmd = cmds->lookup(av[0]);
28
2/2
✓ Branch 9 → 10 taken 35 times.
✓ Branch 9 → 31 taken 8014 times.
8049 if (!cmd) {
29 35 const char *name = av[0];
30
2/2
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 12 taken 33 times.
35 if (!runner->lookup_alias) {
31 2 return error_msg_for_cmd(ebuf, NULL, "No such command: %s", name);
32 }
33
34 33 const char *alias_value = runner->lookup_alias(e, name);
35
2/2
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 31 times.
33 if (unlikely(!alias_value)) {
36 2 return error_msg_for_cmd(ebuf, NULL, "No such command or alias: %s", name);
37 }
38
39 31 PointerArray array = ptr_array_new(0);
40 31 CommandParseError err = parse_commands(runner, &array, alias_value);
41
2/2
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 21 taken 30 times.
31 if (unlikely(err != CMDERR_NONE)) {
42 1 const char *err_msg = command_parse_error_to_string(err);
43 1 ptr_array_free(&array);
44 1 return error_msg_for_cmd(ebuf, NULL, "Parsing alias %s: %s", name, err_msg);
45 }
46
47 // Remove NULL
48 30 array.count--;
49
50
2/2
✓ Branch 25 → 22 taken 35 times.
✓ Branch 25 → 26 taken 30 times.
65 for (size_t i = 1; av[i]; i++) {
51 35 ptr_array_append(&array, xstrdup(av[i]));
52 }
53 30 ptr_array_append(&array, NULL);
54
55 30 bool r = run_commands(runner, &array);
56 30 ptr_array_free(&array);
57 30 return r;
58 }
59
60
4/4
✓ Branch 31 → 32 taken 7364 times.
✓ Branch 31 → 34 taken 650 times.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 34 taken 7363 times.
8014 if (unlikely(ebuf->sourcepos.filename && !(cmd->cmdopts & CMDOPT_ALLOW_IN_RC))) {
61 1 return error_msg_for_cmd(ebuf, NULL, "Command %s not allowed in config file", cmd->name);
62 }
63
64 // Record command in macro buffer, if recording (this needs to be done
65 // before parse_args() mutates the array)
66
3/4
✓ Branch 34 → 35 taken 5 times.
✓ Branch 34 → 37 taken 8008 times.
✓ Branch 35 → 36 taken 5 times.
✗ Branch 35 → 37 not taken.
8013 if ((runner->flags & CMDRUNNER_ALLOW_RECORDING) && runner->cmds->macro_record) {
67 5 runner->cmds->macro_record(e, cmd, av + 1);
68 }
69
70 // By default change can't be merged with previous one.
71 // Any command can override this by calling begin_change() again.
72 8013 begin_change(CHANGE_MERGE_NONE);
73
74 8013 CommandArgs a = cmdargs_new(av + 1);
75
4/4
✓ Branch 39 → 40 taken 8006 times.
✓ Branch 39 → 43 taken 7 times.
✓ Branch 41 → 42 taken 137 times.
✓ Branch 41 → 43 taken 7869 times.
8013 bool r = likely(parse_args(cmd, &a, ebuf)) && command_func_call(e, ebuf, cmd, &a);
76
77 8013 end_change();
78 8013 return r;
79 }
80
81 // Recursion is limited by MAX_RECURSION_DEPTH
82 // NOLINTNEXTLINE(misc-no-recursion)
83 9482 static bool run_commands(CommandRunner *runner, const PointerArray *array)
84 {
85
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 9481 times.
9482 if (unlikely(runner->recursion_count > MAX_RECURSION_DEPTH)) {
86 1 return error_msg_for_cmd(runner->ebuf, NULL, "alias recursion limit reached");
87 }
88
89 9481 bool stop_at_first_err = (runner->flags & CMDRUNNER_STOP_AT_FIRST_ERROR);
90 9481 void **ptrs = array->ptrs;
91 9481 size_t len = array->count;
92 9481 size_t nfailed = 0;
93 9481 BUG_ON(len == 0);
94 9481 BUG_ON(ptrs[len - 1] != NULL);
95 9481 runner->recursion_count++;
96
97
2/2
✓ Branch 18 → 11 taken 9578 times.
✓ Branch 18 → 19 taken 9479 times.
19057 for (size_t s = 0, e = 0; s < len; ) {
98 // Iterate over strings, until a terminating NULL is encountered
99 37789 while (ptrs[e]) {
100 28211 e++;
101 37789 BUG_ON(e >= len);
102 }
103
104 // If the value of `e` (end) changed, there's a run of at least
105 // 1 string, which is a command followed by 0 or more arguments
106
2/2
✓ Branch 12 → 13 taken 8049 times.
✓ Branch 12 → 17 taken 1529 times.
9578 if (e != s) {
107
2/2
✓ Branch 14 → 15 taken 167 times.
✓ Branch 14 → 17 taken 7882 times.
8049 if (!run_command(runner, (char**)ptrs + s)) {
108 167 nfailed++;
109
2/2
✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 17 taken 165 times.
167 if (stop_at_first_err) {
110 2 goto out;
111 }
112 }
113 }
114
115 // Skip past the NULL, onto the next command (if any)
116 9576 s = ++e;
117 }
118
119 9479 out:
120 9481 runner->recursion_count--;
121 9481 return (nfailed == 0);
122 }
123
124 9457 bool handle_command(CommandRunner *runner, const char *cmd)
125 {
126 9457 BUG_ON(runner->recursion_count != 0);
127 9457 PointerArray array = ptr_array_new(0);
128 9457 CommandParseError err = parse_commands(runner, &array, cmd);
129 9457 bool r;
130
2/2
✓ Branch 6 → 7 taken 9452 times.
✓ Branch 6 → 10 taken 5 times.
9457 if (likely(err == CMDERR_NONE)) {
131 9452 r = run_commands(runner, &array);
132 9452 BUG_ON(runner->recursion_count != 0);
133 } else {
134 5 const char *str = command_parse_error_to_string(err);
135 5 error_msg_for_cmd(runner->ebuf, NULL, "Command syntax error: %s", str);
136 5 r = false;
137 }
138 9457 ptr_array_free(&array);
139 9457 return r;
140 }
141
142 120 void check_cmds_array(const Command cmds[], size_t ncmds, const char *array_name)
143 {
144 120 BUG_ON(ncmds == 0);
145 120 BUG_ON(array_name[0] == '\0');
146
147 if (!DEBUG_ASSERTIONS_ENABLED) {
148 return;
149 }
150
151 // See also: check_bsearch_array() and check_array()
152
2/2
✓ Branch 34 → 6 taken 3192 times.
✓ Branch 34 → 35 taken 120 times.
3312 for (size_t i = 0; i < ncmds; i++) {
153 3192 const char *name = cmds[i].name;
154 3192 const char *flags = cmds[i].flags;
155
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 3192 times.
3192 if (name[0] == '\0') {
156 BUG("Empty string at %s[%zu].name", array_name, i);
157 }
158
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3192 times.
3192 if (name[sizeof(cmds[0].name) - 1] != '\0') {
159 BUG("String sentinel missing from %s[%zu].name", array_name, i);
160 }
161
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3192 times.
3192 if (flags[ARRAYLEN(cmds[0].flags) - 1] != '\0') {
162 BUG("String sentinel missing from %s[%zu].flags", array_name, i);
163 }
164
165
3/4
✓ Branch 12 → 13 taken 24 times.
✓ Branch 12 → 15 taken 3168 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 24 times.
3192 if (is_option(name) && !streq(name, "syntax")) {
166 // Preventing this isn't strictly necessary, but not having
167 // commands and options with the same name makes links and
168 // cross-references in documentation less error prone
169 BUG("command and option with identical name: \"%s\"", name);
170 }
171
172
3/4
✓ Branch 15 → 16 taken 3072 times.
✓ Branch 15 → 29 taken 120 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 29 taken 3072 times.
3192 if (i > 0 && strcmp(name, cmds[i - 1].name) <= 0) {
173 BUG (
174 "String at %s[%zu].name not in sorted order: \"%s\" (prev: \"%s\")",
175 array_name, i, name, cmds[i - 1].name
176 );
177 }
178
179 unsigned char prev_flag = 0;
180 size_t nr_real_flags = 0;
181
182
2/2
✓ Branch 30 → 18 taken 5328 times.
✓ Branch 30 → 31 taken 3192 times.
8520 for (size_t j = 0; flags[j]; j++) {
183 5328 unsigned char flag = flags[j];
184
2/2
✓ Branch 18 → 19 taken 144 times.
✓ Branch 18 → 23 taken 5184 times.
5328 if (flag == '=') {
185
2/4
✓ Branch 19 → 20 taken 144 times.
✗ Branch 19 → 22 not taken.
✓ Branch 20 → 21 taken 144 times.
✗ Branch 20 → 22 not taken.
144 if (j && flags[j - 1] != '=') {
186 144 continue;
187 }
188 BUG("invalid = in %s[%zu].flags (%s): %s", array_name, i, name, flags);
189 }
190
191
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 5184 times.
5184 if (!ascii_isalnum(flag)) {
192 BUG("invalid command flag: 0x%02hhX", flag);
193 }
194
195
1/2
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 5184 times.
5184 if (prev_flag >= flag) { // Using >= here also catches duplicate flags
196 BUG (
197 "flags -%c and -%c not in sorted order for %s[%zu] (%s): %s",
198 flag, prev_flag, array_name, i, name, flags
199 );
200 }
201
202 5184 nr_real_flags++;
203 5184 prev_flag = flag;
204 }
205
206 // Check that the number of real flags (not including '=') fits
207 // in the CommandArgs::flags array and leaves 1 byte for the
208 // null terminator
209 3192 CommandArgs a;
210 3192 BUG_ON(nr_real_flags >= ARRAYLEN(a.flags));
211 }
212 }
213