blob: 164968e634c9ed976cfec8c95cc56a739f39a858 [file] [log] [blame]
#ifndef TESTS_TEST_TOOLS_H
#define TESTS_TEST_TOOLS_H
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <proton/condition.h>
#include <proton/connection_driver.h>
#include <proton/delivery.h>
#include <proton/event.h>
#include <proton/link.h>
#include <proton/message.h>
#include <proton/proactor.h>
#include <proton/transport.h>
#include <proton/type_compat.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Handy way to make pn_bytes: PN_BYTES_LITERAL(FOO) => pn_bytes("FOO",3) */
#define PN_BYTES_LITERAL(X) (pn_bytes(sizeof(#X)-1, #X))
/* A struct to collect the results of a test, created by RUN_TEST macro. */
typedef struct test_t {
const char* name;
int errors;
uintptr_t data; /* Test can store some non-error data here */
bool inverted; /* An inverted test prints no output and reports failure if it passes */
} test_t;
/* Internal, use macros. Print error message and increase the t->errors count.
All output from test macros goes to stderr so it interleaves with PN_TRACE logs.
*/
void test_vlogf_(test_t *t, const char *prefix, const char* expr,
const char* file, int line, const char *fmt, va_list ap)
{
if (t->inverted) return;
fprintf(stderr, "%s:%d", file, line);
if (prefix && *prefix) fprintf(stderr, ": %s", prefix);
if (expr && *expr) fprintf(stderr, ": %s", expr);
if (fmt && *fmt) {
fprintf(stderr, ": ");
vfprintf(stderr, fmt, ap);
}
if (t) fprintf(stderr, " [%s]", t->name);
fprintf(stderr, "\n");
fflush(stderr);
}
void test_logf_(test_t *t, const char *prefix, const char* expr,
const char* file, int line, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
test_vlogf_(t, prefix, expr, file, line, fmt, ap);
va_end(ap);
}
void test_errorf_(test_t *t, const char* expr,
const char* file, int line, const char *fmt, ...) {
++t->errors;
va_list ap;
va_start(ap, fmt);
test_vlogf_(t, "error", expr, file, line, fmt, ap);
va_end(ap);
}
bool test_check_(test_t *t, bool expr, const char *sexpr,
const char *file, int line, const char* fmt, ...) {
if (!expr) {
++t->errors;
va_list ap;
va_start(ap, fmt);
test_vlogf_(t, "check failed", sexpr, file, line, fmt, ap);
va_end(ap);
}
return expr;
}
/* Call via TEST_ASSERT macros */
void assert_fail_(const char* expr, const char* file, int line, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
test_vlogf_(NULL, "assertion failed", expr, file, line, fmt, ap);
va_end(ap);
abort();
}
/* Unconditional assert (does not depend on NDEBUG) for tests. */
#define TEST_ASSERT(expr) \
((expr) ? (void)0 : assert_fail_(#expr, __FILE__, __LINE__, NULL))
/* Unconditional assert with printf-style message (does not depend on NDEBUG) for tests. */
#define TEST_ASSERTF(expr, ...) \
((expr) ? (void)0 : assert_fail_(#expr, __FILE__, __LINE__, __VA_ARGS__))
/* Like TEST_ASSERT but includes errno string for err */
/* TODO aconway 2017-02-16: not thread safe, replace with safe strerror_r or similar */
#define TEST_ASSERT_ERRNO(expr, err) \
TEST_ASSERTF((expr), "%s", strerror(err))
/* Print a message but don't mark the test as having an error */
#define TEST_LOGF(TEST, ...) \
test_logf_((TEST), "info", NULL, __FILE__, __LINE__, __VA_ARGS__)
/* Print an error with printf-style message, increment TEST->errors */
#define TEST_ERRORF(TEST, ...) \
test_errorf_((TEST), NULL, __FILE__, __LINE__, __VA_ARGS__)
/* If EXPR is false, print and record an error for t including #EXPR */
#define TEST_CHECKF(TEST, EXPR, ...) \
test_check_((TEST), (EXPR), #EXPR, __FILE__, __LINE__, __VA_ARGS__)
/* If EXPR is false, print and record an error for t including EXPR */
#define TEST_CHECK(TEST, EXPR) \
test_check_((TEST), (EXPR), #EXPR, __FILE__, __LINE__, "")
/* If EXPR is false, print and record an error for t NOT including #EXPR */
#define TEST_CHECKNF(TEST, EXPR, ...) \
test_check_((TEST), (EXPR), NULL, __FILE__, __LINE__, __VA_ARGS__)
bool test_etype_equal_(test_t *t, pn_event_type_t want, pn_event_type_t got, const char *file, int line) {
return test_check_(t, want == got, NULL, file, line, "want %s got %s",
pn_event_type_name(want),
pn_event_type_name(got));
}
#define TEST_ETYPE_EQUAL(TEST, WANT, GOT) test_etype_equal_((TEST), (WANT), (GOT), __FILE__, __LINE__)
bool test_int_equal_(test_t *t, int want, int got, const char *file, int line) {
return test_check_(t, want == got, NULL, file, line, "want %d, got %d", want, got);
}
#define TEST_INT_EQUAL(TEST, WANT, GOT) test_int_equal_((TEST), (WANT), (GOT), __FILE__, __LINE__)
bool test_size_equal_(test_t *t, size_t want, size_t got, const char *file, int line) {
return test_check_(t, want == got, NULL, file, line, "want %zd, got %zd", want, got);
}
#define TEST_SIZE_EQUAL(TEST, WANT, GOT) test_size_equal_((TEST), (WANT), (GOT), __FILE__, __LINE__)
bool test_str_equal_(test_t *t, const char* want, const char* got, const char *file, int line) {
return test_check_(t, !strcmp(want, got), NULL, file, line, "want '%s', got '%s'", want, got);
}
#define TEST_STR_EQUAL(TEST, WANT, GOT) test_str_equal_((TEST), (WANT), (GOT), __FILE__, __LINE__)
#define TEST_INSPECT(TEST, WANT, GOT) do { \
pn_string_t *s = pn_string(NULL); \
TEST_ASSERT(0 == pn_inspect(GOT, s)); \
TEST_STR_EQUAL((TEST), (WANT), pn_string_get(s)); \
pn_free(s); \
} while (0)
#define TEST_STR_IN(TEST, WANT, GOT) \
test_check_((TEST), strstr((GOT), (WANT)), NULL, __FILE__, __LINE__, "'%s' not in '%s'", (WANT), (GOT))
#define TEST_COND_EMPTY(TEST, C) \
TEST_CHECKNF((TEST), (!(C) || !pn_condition_is_set(C)), "Unexpected condition - %s:%s", \
pn_condition_get_name(C), pn_condition_get_description(C))
#define TEST_COND_DESC(TEST, WANT, C) \
(TEST_CHECKNF(t, pn_condition_is_set((C)), "No condition, expected :%s", (WANT)) ? \
TEST_STR_IN(t, (WANT), pn_condition_get_description(C)) : 0);
#define TEST_COND_NAME(TEST, WANT, C) \
(TEST_CHECKNF(t, pn_condition_is_set((C)), "No condition, expected %s:", (WANT)) ? \
TEST_STR_EQUAL(t, (WANT), pn_condition_get_name(C)) : 0);
#define TEST_CONDITION(TEST, NAME, DESC, C) do { \
TEST_COND_NAME(TEST, NAME, C); \
TEST_COND_DESC(TEST, DESC, C); \
} while(0)
/* T is name of a test_t variable, EXPR is the test expression (which should update T)
FAILED is incremented if the test has errors
*/
#define RUN_TEST(FAILED, T, EXPR) do { \
fprintf(stderr, "TEST: %s\n", #EXPR); \
fflush(stdout); \
test_t T = { #EXPR, 0 }; \
(EXPR); \
if (T.errors && !t.inverted) { \
fprintf(stderr, "FAIL: %s (%d errors)\n", #EXPR, T.errors); \
++(FAILED); \
} else if (!T.errors && t.inverted) { \
fprintf(stderr, "UNEXPECTED PASS: %s", #EXPR); \
++(FAILED); \
} \
} while(0)
/* Like RUN_TEST but only if one of the argv strings is found in the test EXPR */
#define RUN_ARGV_TEST(FAILED, T, EXPR) do { \
if (argc == 1) { \
RUN_TEST(FAILED, T, EXPR); \
} else { \
for (int i = 1; i < argc; ++i) { \
if (strstr(#EXPR, argv[i])) { \
RUN_TEST(FAILED, T, EXPR); \
break; \
} \
} \
} \
} while(0)
/* Ensure buf has at least size bytes, use realloc if need be */
void rwbytes_ensure(pn_rwbytes_t *buf, size_t size) {
if (buf->start == NULL || buf->size < size) {
buf->start = (char*)realloc(buf->start, size);
buf->size = size;
}
}
static const size_t BUF_MIN = 1024;
/* Encode message m into buffer buf, return the size.
* The buffer is expanded using realloc() if needed.
*/
size_t message_encode(pn_message_t* m, pn_rwbytes_t *buf) {
int err = 0;
rwbytes_ensure(buf, BUF_MIN);
size_t size = buf->size;
while ((err = pn_message_encode(m, buf->start, &size)) != 0) {
if (err == PN_OVERFLOW) {
rwbytes_ensure(buf, buf->size * 2);
size = buf->size;
} else {
TEST_ASSERTF(err == 0, "encoding: %s %s", pn_code(err), pn_error_text(pn_message_error(m)));
}
}
return size;
}
/* Decode message from delivery d into message m.
* Use buf to hold intermediate message data, expand with realloc() if needed.
*/
void message_decode(pn_message_t *m, pn_delivery_t *d, pn_rwbytes_t *buf) {
pn_link_t *l = pn_delivery_link(d);
ssize_t size = pn_delivery_pending(d);
rwbytes_ensure(buf, size);
ssize_t result = pn_link_recv(l, buf->start, size);
TEST_ASSERTF(size == result, "%ld != %ld", (long)size, (long)result);
pn_message_clear(m);
TEST_ASSERTF(!pn_message_decode(m, buf->start, size), "decode: %s", pn_error_text(pn_message_error(m)));
}
#endif // TESTS_TEST_TOOLS_H