/* ** Copyright 2004-2005 The Apache Software Foundation ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include "apr_file_io.h" #include "at.h" #include "apr_lib.h" #include "apr_strings.h" #include "apr_tables.h" #include "apr_env.h" #include apr_status_t at_begin(at_t *t, int total) { char buf[32]; apr_snprintf(buf, 32, "1..%d", total); return at_report(t, buf); } static apr_status_t test_cleanup(void *data) { at_t *t = data; if (t->current < t->plan) return at_report(t, "Bail out!"); else return at_report(t, "END"); } void at_end(at_t *t) { apr_pool_cleanup_kill(t->pool, t, test_cleanup); test_cleanup(t); } apr_status_t at_comment(at_t *t, const char *fmt, va_list vp) { apr_status_t s; char buf[256], *b = buf + 2; char *end; int rv; rv = apr_vsnprintf(b, 250, fmt, vp); if (rv <= 0) return APR_EINVAL; end = b + rv; buf[0] = '#'; buf[1] = ' '; if (rv == 250) { end[-1] = '.'; *end++ = '.'; *end++ = '.'; *end++ = '\n'; *end = 0; } else if (end[-1] != '\n') { *end++ = '\n'; *end = 0; } b = buf; while (1) { char *eol; eol = strchr(b + 2, '\n'); *eol = 0; s = at_report(t, b); if (s != APR_SUCCESS || eol == end - 1) break; b = eol - 1; b[0] = '#'; b[1] = ' '; } return s; } void at_ok(at_t *t, int is_ok, const char *label, const char *file, int line) { char format[] = "not ok %d - %s # %s (%s:%d) test %d in %s"; char *end = format + 10; char *fmt = is_ok ? format + 4 : format; char buf[256]; const char *comment = NULL; int rv, is_fatal = 0, is_skip = 0, is_todo = 0; t->current++; if (*t->fatal == t->current) { t->fatal++; is_fatal = 1; } if (*t->skip == t->current) { t->skip++; is_skip = 1; } if (*t->todo == t->current) { t->todo++; is_todo = 1; } if (AT_FLAG_CONCISE(t->flags)) format[9] = '\0'; else if (is_ok && !AT_FLAG_TRACE(t->flags)) format[14] = '\0'; else if (is_fatal && ! is_ok) comment = "fatal"; else comment = is_todo ? "todo" : is_skip ? "skip" : "at"; rv = apr_snprintf(buf, 256, fmt, t->current + t->prior, label, comment, file, line, t->current, t->name); if (rv <= 0) exit(-1); end = buf + rv; if (rv == 250) { *end++ = '.'; *end++ = '.'; *end++ = '.'; *end = '\0'; } if (memchr(buf, '\n', rv) != NULL || at_report(t, buf) != APR_SUCCESS) exit(-1); if (!is_ok && is_fatal) { while (t->current++ < t->plan) { apr_snprintf(buf, 256, "not ok %d # skipped: aborting test %s", t->prior + t->current, t->name); at_report(t, buf); } longjmp(*t->abort, 0); } } struct at_report_file { at_report_t module; apr_file_t *file; }; static apr_status_t at_report_file_write(at_report_t *ctx, const char *msg) { struct at_report_file *r = (struct at_report_file *)ctx; apr_file_t *f = r->file; apr_size_t len = strlen(msg); apr_status_t s; s = apr_file_write_full(f, msg, len, &len); if (s != APR_SUCCESS) return s; s = apr_file_putc('\n', f); if (s != APR_SUCCESS) return s; return apr_file_flush(f); } at_report_t *at_report_file_make(apr_pool_t *p, apr_file_t *f) { struct at_report_file *r = apr_palloc(p, sizeof *r); r->module.func = at_report_file_write; r->file = f; return &r->module; } struct at_report_local { at_report_t module; at_t *t; at_report_t *saved_report; const int *saved_fatal; int dummy_fatal; const char *file; int line; int passed; apr_pool_t *pool; }; static apr_status_t report_local_cleanup(void *data) { struct at_report_local *q = data; dAT = q->t; char label[32]; apr_snprintf(label, 32, "collected %d passing tests", q->passed); AT->report = q->saved_report; AT->fatal = q->saved_fatal; at_ok(q->t, 1, label, q->file, q->line); return APR_SUCCESS; } static apr_status_t at_report_local_write(at_report_t *ctx, const char *msg) { char buf[256]; struct at_report_local *q = (struct at_report_local *)ctx; dAT = q->t; if (strncmp(msg, "not ok", 6) == 0) { q->saved_report->func(q->saved_report, msg); AT->report = q->saved_report; AT->fatal = q->saved_fatal; apr_pool_cleanup_kill(q->pool, q, report_local_cleanup); while (AT->current++ < AT->plan) { apr_snprintf(buf, 256, "not ok %d # skipped: aborting test %s", AT->prior + AT->current, AT->name); at_report(AT, buf); } longjmp(*AT->abort, 0); } else if (strncmp(msg, "ok", 2) == 0) { AT->current--; q->passed++; } return APR_SUCCESS; } void at_report_local(at_t *AT, apr_pool_t *p, const char *file, int line) { struct at_report_local *q = apr_palloc(p, sizeof *q); q->module.func = at_report_local_write; q->t = AT; q->saved_report = AT->report; q->saved_fatal = AT->fatal; q->dummy_fatal = 0; q->file = apr_pstrdup(p, file); q->line = line; q->passed = 0; q->pool = p; AT->fatal = &q->dummy_fatal; AT->report = &q->module; if (*q->saved_fatal == AT->current + 1) q->saved_fatal++; apr_pool_cleanup_register(p, q, report_local_cleanup, report_local_cleanup); } at_t *at_create(apr_pool_t *pool, unsigned char flags, at_report_t *report) { at_t *t = apr_pcalloc(pool, sizeof *t); t->flags = flags; t->report = report; t->pool = pool; apr_pool_cleanup_register(pool, t, test_cleanup, test_cleanup); return t; } #define AT_NELTS 4 static int* at_list(apr_pool_t *pool, const char *spec, int *list) { apr_array_header_t arr; int prev, current = 0; arr.pool = pool; arr.elt_size = sizeof *list; arr.nelts = 0; arr.nalloc = AT_NELTS; arr.elts = (char *)list; do { while (*spec && !apr_isdigit(*spec)) ++spec; prev = current; current = (int)apr_strtoi64(spec, (char **)(void *)&spec, 10); *(int *)apr_array_push(&arr) = current; } while (prev >= current); return (int *)arr.elts; } apr_status_t at_run(at_t *AT, const at_test_t *test) { int dummy = 0, fbuf[AT_NELTS], sbuf[AT_NELTS], tbuf[AT_NELTS]; jmp_buf j; AT->current = 0; AT->prior += AT->plan; AT->name = test->name; AT->plan = test->plan; if (test->fatals) AT->fatal = at_list(AT->pool, test->fatals, fbuf); else AT->fatal = &dummy; if (test->skips) AT->skip = at_list(AT->pool, test->skips, sbuf); else AT->skip = &dummy; if (test->todos) AT->todo = at_list(AT->pool, test->todos, tbuf); else AT->todo = &dummy; AT->abort = &j; if (setjmp(j) == 0) { test->func(AT); return APR_SUCCESS; } AT->abort = NULL; return APR_EGENERAL; }