1 /*
2 **  Licensed to the Apache Software Foundation (ASF) under one or more
3 ** contributor license agreements.  See the NOTICE file distributed with
4 ** this work for additional information regarding copyright ownership.
5 ** The ASF licenses this file to You under the Apache License, Version 2.0
6 ** (the "License"); you may not use this file except in compliance with
7 ** the License.  You may obtain a copy of the License at
8 **
9 **      http://www.apache.org/licenses/LICENSE-2.0
10 **
11 **  Unless required by applicable law or agreed to in writing, software
12 **  distributed under the License is distributed on an "AS IS" BASIS,
13 **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 **  See the License for the specific language governing permissions and
15 **  limitations under the License.
16 */
17 
18 #include "apr_file_io.h"
19 #include "at.h"
20 #include "apr_lib.h"
21 #include "apr_strings.h"
22 #include "apr_tables.h"
23 #include "apr_env.h"
24 #include <assert.h>
25 
26 
at_begin(at_t * t,int total)27 apr_status_t at_begin(at_t *t, int total)
28 {
29     char buf[32];
30     apr_snprintf(buf, 32, "1..%d", total);
31     return at_report(t, buf);
32 }
33 
test_cleanup(void * data)34 static apr_status_t test_cleanup(void *data)
35 {
36     at_t *t = data;
37     if (t->current < t->plan)
38         return at_report(t, "Bail out!");
39     else
40         return at_report(t, "END");
41 }
42 
at_end(at_t * t)43 void at_end(at_t *t)
44 {
45     apr_pool_cleanup_kill(t->pool, t, test_cleanup);
46     test_cleanup(t);
47 }
48 
at_comment(at_t * t,const char * fmt,va_list vp)49 apr_status_t at_comment(at_t *t, const char *fmt, va_list vp)
50 {
51     apr_status_t s;
52     char buf[256], *b = buf + 2;
53     char *end;
54     int rv;
55     rv = apr_vsnprintf(b, 250, fmt, vp);
56 
57     if (rv <= 0)
58         return APR_EINVAL;
59 
60 
61     end = b + rv;
62 
63     buf[0] = '#';
64     buf[1] = ' ';
65 
66     if (rv == 250) {
67         end[-1] = '.';
68         *end++ = '.';
69         *end++ = '.';
70         *end++ = '\n';
71         *end = 0;
72     }
73     else if (end[-1] != '\n') {
74         *end++ = '\n';
75         *end = 0;
76     }
77 
78     b = buf;
79     while (1) {
80         char *eol;
81 
82         eol = strchr(b + 2, '\n');
83         *eol = 0;
84         s = at_report(t, b);
85         if (s != APR_SUCCESS || eol == end - 1)
86             break;
87 
88         b    = eol - 1;
89         b[0] = '#';
90         b[1] = ' ';
91     }
92 
93     return s;
94 }
95 
at_ok(at_t * t,int is_ok,const char * label,const char * file,int line)96 void at_ok(at_t *t, int is_ok, const char *label, const char *file, int line)
97 {
98     char format[] = "not ok %d - %s # %s (%s:%d) test %d in %s";
99     char *end = format + 10;
100     char *fmt = is_ok ? format + 4 : format;
101     char buf[256];
102     const char *comment = NULL;
103     int rv, is_fatal = 0, is_skip = 0, is_todo = 0;
104 
105     t->current++;
106 
107     if (*t->fatal == t->current) {
108         t->fatal++;
109         is_fatal = 1;
110     }
111 
112     if (*t->skip == t->current) {
113         t->skip++;
114         is_skip = 1;
115     }
116 
117     if (*t->todo == t->current) {
118         t->todo++;
119         is_todo = 1;
120     }
121 
122     if (AT_FLAG_CONCISE(t->flags))
123         format[9] = '\0';
124     else if (is_ok && !AT_FLAG_TRACE(t->flags))
125         format[14] = '\0';
126     else if (is_fatal && ! is_ok)
127         comment = "fatal";
128     else
129         comment = is_todo ? "todo" : is_skip ? "skip" : "at";
130 
131     rv = apr_snprintf(buf, 256, fmt, t->current + t->prior,
132                       label, comment,  file, line, t->current, t->name);
133 
134     if (rv <= 0)
135         exit(-1);
136 
137     end = buf + rv;
138 
139     if (rv == 250) {
140         *end++ = '.';
141         *end++ = '.';
142         *end++ = '.';
143         *end = '\0';
144     }
145 
146     if (memchr(buf, '\n', rv) != NULL || at_report(t, buf) != APR_SUCCESS)
147         exit(-1);
148 
149     if (!is_ok && is_fatal) {
150         while (t->current++ < t->plan) {
151             apr_snprintf(buf, 256, "not ok %d # skipped: aborting test %s",
152                      t->prior + t->current, t->name);
153             at_report(t, buf);
154         }
155         longjmp(*t->abort, 0);
156     }
157 }
158 
159 struct at_report_file {
160     at_report_t module;
161     apr_file_t *file;
162 };
163 
164 
at_report_file_write(at_report_t * ctx,const char * msg)165 static apr_status_t at_report_file_write(at_report_t *ctx, const char *msg)
166 {
167     struct at_report_file *r = (struct at_report_file *)ctx;
168     apr_file_t *f = r->file;
169     apr_size_t len = strlen(msg);
170     apr_status_t s;
171 
172     s = apr_file_write_full(f, msg, len, &len);
173     if (s != APR_SUCCESS)
174         return s;
175 
176     s = apr_file_putc('\n', f);
177     if (s != APR_SUCCESS)
178         return s;
179 
180     return apr_file_flush(f);
181 }
182 
at_report_file_make(apr_pool_t * p,apr_file_t * f)183 at_report_t *at_report_file_make(apr_pool_t *p, apr_file_t *f)
184 {
185     struct at_report_file *r = apr_palloc(p, sizeof *r);
186     r->module.func = at_report_file_write;
187     r->file = f;
188     return &r->module;
189 }
190 
191 
192 
193 struct at_report_local {
194     at_report_t  module;
195     at_t        *t;
196     at_report_t *saved_report;
197     const int   *saved_fatal;
198     int          dummy_fatal;
199     const char  *file;
200     int          line;
201     int          passed;
202     apr_pool_t  *pool;
203 };
204 
205 
report_local_cleanup(void * data)206 static apr_status_t report_local_cleanup(void *data)
207 {
208     struct at_report_local *q = data;
209     dAT = q->t;
210     char label[32];
211 
212     apr_snprintf(label, 32, "collected %d passing tests", q->passed);
213 
214     AT->report = q->saved_report;
215     AT->fatal = q->saved_fatal;
216 
217     at_ok(q->t, 1, label, q->file, q->line);
218     return APR_SUCCESS;
219 }
220 
221 
at_report_local_write(at_report_t * ctx,const char * msg)222 static apr_status_t at_report_local_write(at_report_t *ctx, const char *msg)
223 {
224     char buf[256];
225     struct at_report_local *q = (struct at_report_local *)ctx;
226     dAT = q->t;
227 
228     if (strncmp(msg, "not ok", 6) == 0) {
229         q->saved_report->func(q->saved_report, msg);
230         AT->report = q->saved_report;
231         AT->fatal = q->saved_fatal;
232         apr_pool_cleanup_kill(q->pool, q, report_local_cleanup);
233         while (AT->current++ < AT->plan) {
234             apr_snprintf(buf, 256, "not ok %d # skipped: aborting test %s",
235                      AT->prior + AT->current, AT->name);
236             at_report(AT, buf);
237         }
238         longjmp(*AT->abort, 0);
239     }
240     else if (strncmp(msg, "ok", 2) == 0) {
241         AT->current--;
242         q->passed++;
243     }
244     return APR_SUCCESS;
245 }
246 
at_report_local(at_t * AT,apr_pool_t * p,const char * file,int line)247 void at_report_local(at_t *AT, apr_pool_t *p, const char *file, int line)
248 {
249     struct at_report_local *q = apr_palloc(p, sizeof *q);
250     q->module.func = at_report_local_write;
251     q->t = AT;
252     q->saved_report = AT->report;
253     q->saved_fatal = AT->fatal;
254     q->dummy_fatal = 0;
255     q->file = apr_pstrdup(p, file);
256     q->line = line;
257     q->passed = 0;
258     q->pool = p;
259 
260     AT->fatal = &q->dummy_fatal;
261     AT->report = &q->module;
262 
263     if (*q->saved_fatal == AT->current + 1)
264         q->saved_fatal++;
265 
266     apr_pool_cleanup_register(p, q, report_local_cleanup,
267                                     report_local_cleanup);
268 }
269 
270 
at_create(apr_pool_t * pool,unsigned char flags,at_report_t * report)271 at_t *at_create(apr_pool_t *pool, unsigned char flags, at_report_t *report)
272 {
273     at_t *t = apr_pcalloc(pool, sizeof *t);
274     t->flags = flags;
275     t->report = report;
276     t->pool = pool;
277 
278     apr_pool_cleanup_register(pool, t, test_cleanup, test_cleanup);
279     return t;
280 }
281 
282 
283 #define AT_NELTS 4
284 
at_list(apr_pool_t * pool,const char * spec,int * list)285 static int* at_list(apr_pool_t *pool, const char *spec, int *list)
286 {
287     apr_array_header_t arr;
288     int prev, current = 0;
289 
290     arr.pool = pool;
291     arr.elt_size = sizeof *list;
292     arr.nelts = 0;
293     arr.nalloc = AT_NELTS;
294     arr.elts = (char *)list;
295 
296     do {
297         while (*spec && !apr_isdigit(*spec))
298             ++spec;
299 
300         prev = current;
301         current = (int)apr_strtoi64(spec, (char **)(void *)&spec, 10);
302         *(int *)apr_array_push(&arr) = current;
303 
304     } while (prev >= current);
305 
306     return (int *)arr.elts;
307 }
308 
309 
310 
at_run(at_t * AT,const at_test_t * test)311 apr_status_t at_run(at_t *AT, const at_test_t *test)
312 {
313     int dummy = 0, fbuf[AT_NELTS], sbuf[AT_NELTS], tbuf[AT_NELTS];
314     jmp_buf j;
315 
316     AT->current = 0;
317     AT->prior   += AT->plan;
318     AT->name    = test->name;
319     AT->plan    = test->plan;
320 
321     if (test->fatals)
322         AT->fatal = at_list(AT->pool, test->fatals, fbuf);
323     else
324         AT->fatal = &dummy;
325 
326     if (test->skips)
327         AT->skip = at_list(AT->pool, test->skips, sbuf);
328     else
329         AT->skip = &dummy;
330 
331     if (test->todos)
332         AT->todo = at_list(AT->pool, test->todos, tbuf);
333     else
334         AT->todo = &dummy;
335 
336     AT->abort = &j;
337     if (setjmp(j) == 0) {
338         test->func(AT);
339         return APR_SUCCESS;
340     }
341     AT->abort = NULL;
342     return APR_EGENERAL;
343 }
344