| /* |
| * Check: a unit test framework for C |
| * Copyright (C) 2001, 2002 Arien Malec |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "libcompat.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include "internal-check.h" |
| #include "check_error.h" |
| #include "check_list.h" |
| #include "check_impl.h" |
| #include "check_pack.h" |
| |
| #ifndef HAVE_PTHREAD |
| #define pthread_mutex_lock(arg) |
| #define pthread_mutex_unlock(arg) |
| #define pthread_cleanup_push(f,a) { |
| #define pthread_cleanup_pop(e) } |
| #endif |
| |
| /* Maximum size for one message in the message stream. */ |
| #define CK_MAX_MSG_SIZE 8192 |
| /* This is used to implement a sliding window on the receiving |
| * side. When sending messages, we assure that no single message |
| * is bigger than this (actually we check against CK_MAX_MSG_SIZE/2). |
| * The usual size for a message is less than 80 bytes. |
| * All this is done instead of the previous approach to allocate (actually |
| * continuously reallocate) one big chunk for the whole message stream. |
| * Problems were seen in the wild with up to 4 GB reallocations. |
| */ |
| |
| |
| /* typedef an unsigned int that has at least 4 bytes */ |
| typedef uint32_t ck_uint32; |
| |
| |
| static void pack_int (char **buf, int val); |
| static int upack_int (char **buf); |
| static void pack_str (char **buf, const char *str); |
| static char *upack_str (char **buf); |
| |
| static int pack_ctx (char **buf, CtxMsg * cmsg); |
| static int pack_loc (char **buf, LocMsg * lmsg); |
| static int pack_fail (char **buf, FailMsg * fmsg); |
| static int pack_duration (char **buf, DurationMsg * fmsg); |
| static void upack_ctx (char **buf, CtxMsg * cmsg); |
| static void upack_loc (char **buf, LocMsg * lmsg); |
| static void upack_fail (char **buf, FailMsg * fmsg); |
| static void upack_duration (char **buf, DurationMsg * fmsg); |
| |
| static void check_type (int type, const char *file, int line); |
| static enum ck_msg_type upack_type (char **buf); |
| static void pack_type (char **buf, enum ck_msg_type type); |
| |
| static int read_buf (FILE * fdes, int size, char *buf); |
| static int get_result (char *buf, RcvMsg * rmsg); |
| static void rcvmsg_update_ctx (RcvMsg * rmsg, enum ck_result_ctx ctx); |
| static void rcvmsg_update_loc (RcvMsg * rmsg, const char *file, int line); |
| static RcvMsg *rcvmsg_create (void); |
| void rcvmsg_free (RcvMsg * rmsg); |
| |
| typedef int (*pfun) (char **, CheckMsg *); |
| typedef void (*upfun) (char **, CheckMsg *); |
| |
| static pfun pftab[] = { |
| (pfun) pack_ctx, |
| (pfun) pack_fail, |
| (pfun) pack_loc, |
| (pfun) pack_duration |
| }; |
| |
| static upfun upftab[] = { |
| (upfun) upack_ctx, |
| (upfun) upack_fail, |
| (upfun) upack_loc, |
| (upfun) upack_duration |
| }; |
| |
| int |
| pack (enum ck_msg_type type, char **buf, CheckMsg * msg) |
| { |
| if (buf == NULL) |
| return -1; |
| if (msg == NULL) |
| return 0; |
| |
| check_type (type, __FILE__, __LINE__); |
| |
| return pftab[type] (buf, msg); |
| } |
| |
| int |
| upack (char *buf, CheckMsg * msg, enum ck_msg_type *type) |
| { |
| char *obuf; |
| |
| if (buf == NULL) |
| return -1; |
| |
| obuf = buf; |
| |
| *type = upack_type (&buf); |
| |
| check_type (*type, __FILE__, __LINE__); |
| |
| upftab[*type] (&buf, msg); |
| |
| return buf - obuf; |
| } |
| |
| static void |
| pack_int (char **buf, int val) |
| { |
| unsigned char *ubuf = (unsigned char *) *buf; |
| ck_uint32 uval = val; |
| |
| ubuf[0] = (unsigned char) ((uval >> 24) & 0xFF); |
| ubuf[1] = (unsigned char) ((uval >> 16) & 0xFF); |
| ubuf[2] = (unsigned char) ((uval >> 8) & 0xFF); |
| ubuf[3] = (unsigned char) (uval & 0xFF); |
| |
| *buf += 4; |
| } |
| |
| static int |
| upack_int (char **buf) |
| { |
| unsigned char *ubuf = (unsigned char *) *buf; |
| ck_uint32 uval; |
| |
| uval = |
| (ck_uint32) ((ubuf[0] << 24) | (ubuf[1] << 16) | (ubuf[2] << 8) | |
| ubuf[3]); |
| |
| *buf += 4; |
| |
| return (int) uval; |
| } |
| |
| static void |
| pack_str (char **buf, const char *val) |
| { |
| int strsz; |
| |
| if (val == NULL) |
| strsz = 0; |
| else |
| strsz = strlen (val); |
| |
| pack_int (buf, strsz); |
| |
| if (strsz > 0) { |
| memcpy (*buf, val, strsz); |
| *buf += strsz; |
| } |
| } |
| |
| static char * |
| upack_str (char **buf) |
| { |
| char *val; |
| int strsz; |
| |
| strsz = upack_int (buf); |
| |
| if (strsz > 0) { |
| val = (char *) emalloc (strsz + 1); |
| memcpy (val, *buf, strsz); |
| val[strsz] = 0; |
| *buf += strsz; |
| } else { |
| val = (char *) emalloc (1); |
| *val = 0; |
| } |
| |
| return val; |
| } |
| |
| static void |
| pack_type (char **buf, enum ck_msg_type type) |
| { |
| pack_int (buf, (int) type); |
| } |
| |
| static enum ck_msg_type |
| upack_type (char **buf) |
| { |
| return (enum ck_msg_type) upack_int (buf); |
| } |
| |
| |
| static int |
| pack_ctx (char **buf, CtxMsg * cmsg) |
| { |
| char *ptr; |
| int len; |
| |
| len = 4 + 4; |
| *buf = ptr = (char *) emalloc (len); |
| |
| pack_type (&ptr, CK_MSG_CTX); |
| pack_int (&ptr, (int) cmsg->ctx); |
| |
| return len; |
| } |
| |
| static void |
| upack_ctx (char **buf, CtxMsg * cmsg) |
| { |
| cmsg->ctx = (enum ck_result_ctx) upack_int (buf); |
| } |
| |
| static int |
| pack_duration (char **buf, DurationMsg * cmsg) |
| { |
| char *ptr; |
| int len; |
| |
| len = 4 + 4; |
| *buf = ptr = (char *) emalloc (len); |
| |
| pack_type (&ptr, CK_MSG_DURATION); |
| pack_int (&ptr, cmsg->duration); |
| |
| return len; |
| } |
| |
| static void |
| upack_duration (char **buf, DurationMsg * cmsg) |
| { |
| cmsg->duration = upack_int (buf); |
| } |
| |
| static int |
| pack_loc (char **buf, LocMsg * lmsg) |
| { |
| char *ptr; |
| int len; |
| |
| len = 4 + 4 + (lmsg->file ? strlen (lmsg->file) : 0) + 4; |
| *buf = ptr = (char *) emalloc (len); |
| |
| pack_type (&ptr, CK_MSG_LOC); |
| pack_str (&ptr, lmsg->file); |
| pack_int (&ptr, lmsg->line); |
| |
| return len; |
| } |
| |
| static void |
| upack_loc (char **buf, LocMsg * lmsg) |
| { |
| lmsg->file = upack_str (buf); |
| lmsg->line = upack_int (buf); |
| } |
| |
| static int |
| pack_fail (char **buf, FailMsg * fmsg) |
| { |
| char *ptr; |
| int len; |
| |
| len = 4 + 4 + (fmsg->msg ? strlen (fmsg->msg) : 0); |
| *buf = ptr = (char *) emalloc (len); |
| |
| pack_type (&ptr, CK_MSG_FAIL); |
| pack_str (&ptr, fmsg->msg); |
| |
| return len; |
| } |
| |
| static void |
| upack_fail (char **buf, FailMsg * fmsg) |
| { |
| fmsg->msg = upack_str (buf); |
| } |
| |
| static void |
| check_type (int type, const char *file, int line) |
| { |
| if (type < 0 || type >= CK_MSG_LAST) |
| eprintf ("Bad message type arg %d", file, line, type); |
| } |
| |
| #ifdef HAVE_PTHREAD |
| static pthread_mutex_t ck_mutex_lock = PTHREAD_MUTEX_INITIALIZER; |
| static void |
| ppack_cleanup (void *mutex) |
| { |
| pthread_mutex_unlock ((pthread_mutex_t *) mutex); |
| } |
| #endif |
| |
| void |
| ppack (FILE * fdes, enum ck_msg_type type, CheckMsg * msg) |
| { |
| char *buf = NULL; |
| int n; |
| ssize_t r; |
| |
| n = pack (type, &buf, msg); |
| /* Keep it on the safe side to not send too much data. */ |
| if (n > (CK_MAX_MSG_SIZE / 2)) |
| eprintf ("Message string too long", __FILE__, __LINE__ - 2); |
| |
| pthread_cleanup_push (ppack_cleanup, &ck_mutex_lock); |
| pthread_mutex_lock (&ck_mutex_lock); |
| r = fwrite (buf, 1, n, fdes); |
| fflush (fdes); |
| pthread_mutex_unlock (&ck_mutex_lock); |
| pthread_cleanup_pop (0); |
| if (r != n) |
| eprintf ("Error in call to fwrite:", __FILE__, __LINE__ - 2); |
| |
| free (buf); |
| } |
| |
| static int |
| read_buf (FILE * fdes, int size, char *buf) |
| { |
| int n; |
| |
| n = fread (buf, 1, size, fdes); |
| |
| if (ferror (fdes)) { |
| eprintf ("Error in call to fread:", __FILE__, __LINE__ - 4); |
| } |
| |
| return n; |
| } |
| |
| static int |
| get_result (char *buf, RcvMsg * rmsg) |
| { |
| enum ck_msg_type type; |
| CheckMsg msg; |
| int n; |
| |
| n = upack (buf, &msg, &type); |
| if (n == -1) |
| eprintf ("Error in call to upack", __FILE__, __LINE__ - 2); |
| |
| if (type == CK_MSG_CTX) { |
| CtxMsg *cmsg = (CtxMsg *) & msg; |
| |
| rcvmsg_update_ctx (rmsg, cmsg->ctx); |
| } else if (type == CK_MSG_LOC) { |
| LocMsg *lmsg = (LocMsg *) & msg; |
| |
| if (rmsg->failctx == CK_CTX_INVALID) { |
| rcvmsg_update_loc (rmsg, lmsg->file, lmsg->line); |
| } |
| free (lmsg->file); |
| } else if (type == CK_MSG_FAIL) { |
| FailMsg *fmsg = (FailMsg *) & msg; |
| |
| if (rmsg->msg == NULL) { |
| rmsg->msg = strdup (fmsg->msg); |
| rmsg->failctx = rmsg->lastctx; |
| } else { |
| /* Skip subsequent failure messages, only happens for CK_NOFORK */ |
| } |
| free (fmsg->msg); |
| } else if (type == CK_MSG_DURATION) { |
| DurationMsg *cmsg = (DurationMsg *) & msg; |
| |
| rmsg->duration = cmsg->duration; |
| } else |
| check_type (type, __FILE__, __LINE__); |
| |
| return n; |
| } |
| |
| static void |
| reset_rcv_test (RcvMsg * rmsg) |
| { |
| rmsg->test_line = -1; |
| rmsg->test_file = NULL; |
| } |
| |
| static void |
| reset_rcv_fixture (RcvMsg * rmsg) |
| { |
| rmsg->fixture_line = -1; |
| rmsg->fixture_file = NULL; |
| } |
| |
| static RcvMsg * |
| rcvmsg_create (void) |
| { |
| RcvMsg *rmsg; |
| |
| rmsg = (RcvMsg *) emalloc (sizeof (RcvMsg)); |
| rmsg->lastctx = CK_CTX_INVALID; |
| rmsg->failctx = CK_CTX_INVALID; |
| rmsg->msg = NULL; |
| rmsg->duration = -1; |
| reset_rcv_test (rmsg); |
| reset_rcv_fixture (rmsg); |
| return rmsg; |
| } |
| |
| void |
| rcvmsg_free (RcvMsg * rmsg) |
| { |
| free (rmsg->fixture_file); |
| free (rmsg->test_file); |
| free (rmsg->msg); |
| free (rmsg); |
| } |
| |
| static void |
| rcvmsg_update_ctx (RcvMsg * rmsg, enum ck_result_ctx ctx) |
| { |
| if (rmsg->lastctx != CK_CTX_INVALID) { |
| free (rmsg->fixture_file); |
| reset_rcv_fixture (rmsg); |
| } |
| rmsg->lastctx = ctx; |
| } |
| |
| static void |
| rcvmsg_update_loc (RcvMsg * rmsg, const char *file, int line) |
| { |
| if (rmsg->lastctx == CK_CTX_TEST) { |
| free (rmsg->test_file); |
| rmsg->test_line = line; |
| rmsg->test_file = strdup (file); |
| } else { |
| free (rmsg->fixture_file); |
| rmsg->fixture_line = line; |
| rmsg->fixture_file = strdup (file); |
| } |
| } |
| |
| RcvMsg * |
| punpack (FILE * fdes) |
| { |
| int nread, nparse, n; |
| char *buf; |
| RcvMsg *rmsg; |
| |
| rmsg = rcvmsg_create (); |
| |
| /* Allcate a buffer */ |
| buf = (char *) emalloc (CK_MAX_MSG_SIZE); |
| /* Fill the buffer from the file */ |
| nread = read_buf (fdes, CK_MAX_MSG_SIZE, buf); |
| nparse = nread; |
| /* While not all parsed */ |
| while (nparse > 0) { |
| /* Parse one message */ |
| n = get_result (buf, rmsg); |
| nparse -= n; |
| /* Move remaining data in buffer to the beginning */ |
| memmove (buf, buf + n, nparse); |
| /* If EOF has not been seen */ |
| if (nread > 0) { |
| /* Read more data into empty space at end of the buffer */ |
| nread = read_buf (fdes, n, buf + nparse); |
| nparse += nread; |
| } |
| } |
| free (buf); |
| |
| if (rmsg->lastctx == CK_CTX_INVALID) { |
| free (rmsg); |
| rmsg = NULL; |
| } |
| |
| return rmsg; |
| } |