utf8: don't allow NUL in decoded strings.
[ccan] / tools / ccanlint / tests / examples_run.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <tools/tools.h>
3 #include <ccan/foreach/foreach.h>
4 #include <ccan/str/str.h>
5 #include <ccan/tal/str/str.h>
6 #include <ccan/cast/cast.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <stdint.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <ctype.h>
14 #include <assert.h>
15
16 static const char *can_run(struct manifest *m)
17 {
18         struct list_head *list;
19
20         if (safe_mode)
21                 return "Safe mode enabled";
22         foreach_ptr(list, &m->examples, &m->mangled_examples)
23                 if (!list_empty(list))
24                         return NULL;
25         return "No examples";
26 }
27
28 /* Very dumb scanner, allocates %s-strings. */
29 static bool scan_forv(const void *ctx,
30                       const char *input, const char *fmt, va_list *args)
31 {
32         va_list ap;
33         bool ret;
34
35         if (input[0] == '\0' || fmt[0] == '\0')
36                 return input[0] == fmt[0];
37
38         va_copy(ap, *args);
39
40         if (cisspace(fmt[0])) {
41                 /* One format space can swallow many input spaces */
42                 ret = false;
43                 while (cisspace(input[0])) {
44                         if (scan_forv(ctx, ++input, fmt+1, &ap)) {
45                                 ret = true;
46                                 break;
47                         }
48                 }
49         } else if (fmt[0] != '%') {
50                 if (toupper(input[0]) != toupper(fmt[0]))
51                         ret = false;
52                 else
53                         ret = scan_forv(ctx, input+1, fmt+1, &ap);
54         } else {
55                 char **p = va_arg(ap, char **);
56                 unsigned int len;
57
58                 ret = false;
59                 assert(fmt[1] == 's');
60                 for (len = 1; input[len-1]; len++) {
61                         ret = scan_forv(ctx, input + len, fmt+2, &ap);
62                         if (ret) {
63                                 *p = tal_strndup(ctx, input, len);
64                                 ret = true;
65                                 break;
66                         }
67                 }
68         }
69         va_end(ap);
70         return ret;
71 }
72
73 static bool scan_for(const void *ctx, const char *input, const char *fmt, ...)
74 {
75         bool ret;
76         va_list ap;
77
78         va_start(ap, fmt);
79         ret = scan_forv(ctx, input, fmt, &ap);
80         va_end(ap);
81         return ret;
82 }
83
84 static char *find_expect(struct ccan_file *file,
85                          char **lines, char **input,
86                          bool *contains, bool *whitespace, bool *error,
87                          unsigned *line)
88 {
89         char *expect;
90
91         *error = false;
92         for (; lines[*line]; (*line)++) {
93                 char *p = lines[*line] + strspn(lines[*line], " \t");
94                 if (!strstarts(p, "//"))
95                         continue;
96                 p += strspn(p, "/ ");
97
98                 /* With or without input? */
99                 if (strncasecmp(p, "given", strlen("given")) == 0) {
100                         /* Must be of form <given "X"> */
101                         if (!scan_for(file, p, "given \"%s\" %s", input, &p)) {
102                                 *error = true;
103                                 return p;
104                         }
105                 } else {
106                         *input = NULL;
107                 }
108
109                 if (scan_for(file, p, "outputs \"%s\"", &expect)) {
110                         *whitespace = true;
111                         *contains = false;
112                         return expect;
113                 }
114
115                 if (scan_for(file, p, "output contains \"%s\"", &expect)) {
116                         *whitespace = true;
117                         *contains = true;
118                         return expect;
119                 }
120
121                 /* Whitespace-ignoring versions. */
122                 if (scan_for(file, p, "outputs %s", &expect)) {
123                         *whitespace = false;
124                         *contains = false;
125                         return expect;
126                 }
127
128                 if (scan_for(file, p, "output contains %s", &expect)) {
129                         *whitespace = false;
130                         *contains = true;
131                         return expect;
132                 }
133
134                 /* Other malformed line? */
135                 if (*input || !strncasecmp(p, "output", strlen("output"))) {
136                         *error = true;
137                         return p;
138                 }
139         }
140         return NULL;
141 }
142
143 static char *unexpected(struct ccan_file *i, const char *input,
144                         const char *expect, bool contains, bool whitespace)
145 {
146         char *output, *cmd;
147         const char *p;
148         bool ok;
149         unsigned int default_time = default_timeout_ms;
150
151         if (input)
152                 cmd = tal_fmt(i, "echo '%s' | %s %s",
153                               input, i->compiled[COMPILE_NORMAL], input);
154         else
155                 cmd = tal_fmt(i, "%s", i->compiled[COMPILE_NORMAL]);
156
157         output = run_with_timeout(i, cmd, &ok, &default_time);
158         if (!ok)
159                 return tal_fmt(i, "Exited with non-zero status\n");
160
161         /* Substitute \n */
162         while ((p = strstr(expect, "\\n")) != NULL) {
163                 expect = tal_fmt(cmd, "%.*s\n%s", (int)(p - expect), expect,
164                                  p+2);
165         }
166
167         if (!whitespace) {
168                 /* Normalize to spaces. */
169                 expect = tal_strjoin(cmd,
170                                      tal_strsplit(cmd, expect, " \n\t",
171                                                   STR_NO_EMPTY),
172                                      " ", STR_NO_TRAIL);
173                 output = tal_strjoin(cmd,
174                                      tal_strsplit(cmd, output, " \n\t",
175                                                   STR_NO_EMPTY),
176                                      " ", STR_NO_TRAIL);
177         }
178
179         if (contains) {
180                 if (strstr(output, expect))
181                         return NULL;
182         } else {
183                 if (streq(output, expect))
184                         return NULL;
185         }
186
187         return output;
188 }
189
190 static void run_examples(struct manifest *m,
191                          unsigned int *timeleft UNNEEDED, struct score *score)
192 {
193         struct ccan_file *i;
194         struct list_head *list;
195
196         score->total = 0;
197         score->pass = true;
198
199         foreach_ptr(list, &m->examples, &m->mangled_examples) {
200                 list_for_each(list, i, list) {
201                         char **lines, *expect, *input, *output;
202                         unsigned int linenum = 0;
203                         bool contains, whitespace, error;
204
205                         lines = get_ccan_file_lines(i);
206
207                         for (expect = find_expect(i, lines, &input,
208                                                   &contains, &whitespace, &error,
209                                                   &linenum);
210                              expect;
211                              linenum++,
212                                      expect = find_expect(i, lines, &input,
213                                                           &contains, &whitespace,
214                                                           &error, &linenum)) {
215                                 if (error) {
216                                         score_file_error(score, i, linenum+1,
217                                                  "Unparsable test line '%s'",
218                                                          lines[linenum]);
219                                         score->pass = false;
220                                         break;
221                                 }
222
223                                 if (i->compiled[COMPILE_NORMAL] == NULL)
224                                         continue;
225
226                                 score->total++;
227                                 output = unexpected(i, input, expect,
228                                                     contains, whitespace);
229                                 if (!output) {
230                                         score->score++;
231                                         continue;
232                                 }
233                                 score_file_error(score, i, linenum+1,
234                                                  "output '%s' didn't %s '%s'\n",
235                                                  output,
236                                                  contains ? "contain" : "match",
237                                                  expect);
238                                 score->pass = false;
239                         }
240                 }
241         }
242 }
243
244 /* FIXME: Test with reduced features, valgrind! */
245 struct ccanlint examples_run = {
246         .key = "examples_run",
247         .name = "Module examples with expected output give that output",
248         .check = run_examples,
249         .can_run = can_run,
250         .needs = "examples_compile"
251 };
252
253 REGISTER_TEST(examples_run);