/* * %CopyrightBegin% * * Copyright Ericsson AB 2001-2016. All Rights Reserved. * * 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. * * %CopyrightEnd% */ #include #include #include #include #include #include #ifndef __WIN32__ #include #endif #include #include "ei_runner.h" #ifndef __WIN32__ #define _O_BINARY 0 #define _setmode(fd, mode) #endif #define HEADER_SIZE 4 static char* progname; /* Name of this program (from argv[0]). */ static int fd_from_erl; /* File descriptor from Erlang. */ static int fd_to_erl; /* File descriptor to Erlang. */ static int packet_loop(); static void ensure_buf_big_enough(); static int readn(); static void reply(char* buf, unsigned size); static void dump(); void run_tests(char* argv0, TestCase test_cases[], unsigned number) { int i; int n; char* packet; progname = argv0; _setmode(0, _O_BINARY); _setmode(1, _O_BINARY); fd_from_erl = 0; fd_to_erl = 1; packet = read_packet(&n); /* * Dispatch to the appropriate test function. */ i = packet[0] * 256 + packet[1]; if (i >= number) { fprintf(stderr, "%s: bad test case number %d", progname, i); free(packet); exit(1); } else { (*test_cases[i])(); free(packet); } } /*********************************************************************** * * R e a d i n g p a c k e t s * ************************************************************************/ /* * Reads an Erlang term. * * Only accepts 't' (term) or 'e' (end of test), * exits program on error * returns 1 on 'e', 0 on 't' */ int get_bin_term(ei_x_buff* x, ei_term* term) { int len, version; ei_x_free(x); x->buff = read_packet(&len); x->buffsz = len; x->index = 0; switch (x->buff[x->index++]) { case 'e': return 1; case 't': if (ei_decode_version(x->buff, &x->index, &version) < 0 || ei_decode_ei_term(x->buff, &x->index, term) < 0) { fail("Failed to decode term"); exit(0); } return 0; default: fprintf(stderr, "Garbage received: "); dump(x->buff, len, 16); putc('\n', stderr); fail("C program received garbage"); exit(1); } } /* * Reads a packet from Erlang. The packet must be a standard {packet, 2} * packet. This function aborts if any error is detected (including EOF). * * Returns: The number of bytes in the packet. */ char *read_packet(int *len) { unsigned char* io_buf = NULL; /* Buffer for file i/o. */ int i; unsigned char header[HEADER_SIZE]; unsigned packet_length; /* Length of current packet. */ int bytes_read; /* * Read the packet header. */ bytes_read = readn(fd_from_erl, header, HEADER_SIZE); if (bytes_read == 0) { fprintf(stderr, "%s: Unexpected end of file\n", progname); exit(1); } if (bytes_read != HEADER_SIZE) { fprintf(stderr, "%s: Failed to read packet header\n", progname); exit(1); } /* * Get the length of this packet. */ packet_length = 0; for (i = 0; i < HEADER_SIZE; i++) packet_length = (packet_length << 8) | header[i]; if (len) *len=packet_length; /* report length only if caller requested it */ if ((io_buf = (char *) malloc(packet_length)) == NULL) { fprintf(stderr, "%s: insufficient memory for i/o buffer of size %d\n", progname, packet_length); exit(1); } /* * Read the packet itself. */ bytes_read = readn(fd_from_erl, io_buf, packet_length); if (bytes_read != packet_length) { fprintf(stderr, "%s: couldn't read packet of length %d\r\n", progname, packet_length); free(io_buf); exit(1); } return io_buf; } void free_packet(char* packet) { free(packet); } /*********************************************************************** * S e n d i n g r e p l i e s * * The functions below send various types of replies back to Erlang. * Each reply start with a letter indicating the type of reply. * * Reply Translated to on Erlang side * ----- ---------------------------- * [$b|Bytes] {bytes, Bytes} * [$e] eot * [$f] ct:fail() * [$f|Reason] ct:fail(Reason) * [$t|EncodedTerm] {term, Term} * [$N] 'NULL' * [$m|Message] io:format("~s", [Message]) (otherwise ignored) * ***********************************************************************/ /* * This function reports the outcome of a test fail. It is useful if * you implement a test case entirely in C code. * * If the ok argument is zero, a [$f] reply will be sent to the * Erlang side (causing ct:fail() to be called); otherwise, * the atom 'eot' will be sent to Erlang. * * If you need to provide more details on a failure, use the fail() function. */ void do_report(file, line, ok) char* file; int line; int ok; /* Zero if failed; non-zero otherwise. */ { char reason; /*unsigned long ab; unsigned long fb;*/ reason = ok ? 'e' : 'f'; if (!ok) { do_fail(file, line, "Generic failure"); } else { /* release all unallocated blocks */ /*erl_eterm_release();*/ /* check mem usage stats */ /*erl_eterm_statistics(&ab, &fb);*/ /*if ((ab == 0) && (fb == 0) ) {*/ reply(&reason, 1); /*} else { char sbuf[128]; sprintf(sbuf, "still %lu terms allocated," " %lu on freelist at end of test", ab, fb); do_fail(file, line, sbuf); }*/ } } /* * This function causes a call to ct:fail(Reason) on the * Erlang side. */ void do_fail(const char* file, int line, const char* reason, ...) { va_list ap; char sbuf[2048]; char* sp = sbuf; *sp++ = 'f'; sp += sprintf(sp, "%s, line %d: ", file, line); va_start(ap, reason); sp += vsprintf(sp, reason, ap); va_end(ap); reply(sbuf, 1+strlen(sbuf+1)); } /* * This function sends a message to the Erlang side. * The message will be written to the test servers log file, * but will otherwise be completly ignored. */ void message(char* format, ...) { va_list ap; char sbuf[1024]; sbuf[0] = 'm'; va_start(ap, format); vsprintf(sbuf+1, format, ap); va_end(ap); reply(sbuf, 1+strlen(sbuf+1)); } /* * This function sends the given binary term to the Erlang side, * where it will be received as {term, Term} (prefix 't'). */ void send_bin_term(ei_x_buff* x) { ei_x_buff x2; ei_x_new(&x2); x2.buff[x2.index++] = 't'; ei_x_append(&x2, x); reply(x2.buff, x2.index); ei_x_free(&x2); } /* * This function sends a raw buffer of data to the * Erlang side, where it will be received as {bytes, Bytes} (prefix 'b'). */ void send_buffer(char* buf, int size) { char* send_buf; send_buf = (char *) malloc(size+1); send_buf[0] = 'b'; memcpy(send_buf+1, buf, size); reply(send_buf, size+1); free(send_buf); } /*********************************************************************** * * P r i v a t e h e l p e r s * ***********************************************************************/ /* * Sends a packet back to Erlang. */ static void reply(char* reply_buf, unsigned size) { int n; /* Temporary to hold size. */ int i; /* Loop counter. */ char* buf; buf = (char *) malloc(size+HEADER_SIZE); memcpy(buf+HEADER_SIZE, reply_buf, size); /* * Fill the header starting with the least significant byte. */ n = size; for (i = HEADER_SIZE-1; i >= 0; i--) { buf[i] = (char) n; /* Store least significant byte. */ n = n >> 8; } size += HEADER_SIZE; write(fd_to_erl, buf, size); free(buf); } /* * Reads len number of bytes. */ static int readn(fd, buf, len) int fd; /* File descriptor to read from. */ unsigned char *buf; /* Store in this buffer. */ int len; /* Number of bytes to read. */ { int n; /* Byte count in last read call. */ int sofar = 0; /* Bytes read so far. */ do { if ((n = read(fd, buf+sofar, len-sofar)) <= 0) /* error or EOF in read */ return(n); sofar += n; } while (sofar < len); return sofar; } void dump(buf, sz, max) unsigned char* buf; int sz; int max; { int i, imax; char comma[5] = ","; if (!sz) return; if (sz > max) imax = max; else imax = sz; for (i=0; i max) strcpy(comma, ",..."); else comma[0] = 0; } if (isdigit(buf[i])) fprintf(stderr, "%u%s", (int)(buf[i]), comma); else { if (isalpha(buf[i])) { fprintf(stderr, "%c%s", buf[i], comma); } else fprintf(stderr, "%u%s", (int)(buf[i]), comma); } } }