commit dd3444cbc373d0dc5c3695025e1f29dbe63fc2c4 Author: David Goulet dgoulet@ev0ke.net Date: Sat Aug 24 12:31:09 2013 -0400
Remove old tests and add tap library
The old tests were broken with the new code base. Also, the new tests will use libtap that is quite a standard lib. to use for test management providing a known standard output format also. Both C lib. and a bash one is added.
Signed-off-by: David Goulet dgoulet@ev0ke.net --- Makefile.am | 2 +- configure.ac | 4 +- test/Makefile.am | 4 - test/test_torsocks.c | 494 ------------------------------------------- tests/Makefile.am | 1 + tests/utils/Makefile.am | 1 + tests/utils/tap/Makefile.am | 4 + tests/utils/tap/tap.c | 433 +++++++++++++++++++++++++++++++++++++ tests/utils/tap/tap.h | 89 ++++++++ tests/utils/tap/tap.sh | 456 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 988 insertions(+), 500 deletions(-)
diff --git a/Makefile.am b/Makefile.am index 91f4056..e982a1a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ ACLOCAL_AMFLAGS = -I config
-SUBDIRS = src doc +SUBDIRS = src doc tests
dist_doc_DATA = ChangeLog FAQ
diff --git a/configure.ac b/configure.ac index d1726de..4382517 100644 --- a/configure.ac +++ b/configure.ac @@ -371,8 +371,10 @@ AC_CONFIG_FILES([ src/bin/torsocks src/common/Makefile src/lib/Makefile + tests/Makefile + tests/utils/Makefile + tests/utils/tap/Makefile doc/Makefile - test/Makefile ])
AC_OUTPUT diff --git a/test/Makefile.am b/test/Makefile.am deleted file mode 100644 index d42f10e..0000000 --- a/test/Makefile.am +++ /dev/null @@ -1,4 +0,0 @@ -noinst_PROGRAMS= test_torsocks - -test_torsocks_SOURCES= test_torsocks.c -test_torsocks_LDFLAGS= $(TESTLDFLAGS) diff --git a/test/test_torsocks.c b/test/test_torsocks.c deleted file mode 100644 index d9ea32a..0000000 --- a/test/test_torsocks.c +++ /dev/null @@ -1,494 +0,0 @@ -/*************************************************************************** - * * - * Copyright (c) 2000 Alessandro Iurlano. * - * Copyright (C) 2004 Tomasz Kojm tkojm@clamav.net * - * Copyright (C) 2011 Robert Hogan robert@roberthogan.net * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program 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 General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -/* PreProcessor Defines */ -#include <config.h> - -#include <errno.h> -#include <fcntl.h> -#include <netdb.h> -#ifdef OPENBSD -#include <netinet/in_systm.h> -#endif -#include <sys/types.h> -#include <sys/socket.h> -#ifndef HAVE_NETINET_IN_H -#include <netinet/in.h> -#endif -#ifndef HAVE_ARPA_INET_H -#include <arpa/inet.h> -#endif -#include <arpa/nameser.h> -#if defined(__APPLE__) || defined(__darwin__) -#include <arpa/nameser_compat.h> -#endif -#include <netinet/ip.h> -#include <netinet/ip_icmp.h> -#include <pthread.h> -#include <resolv.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/time.h> -#ifdef OPENBSD -#include <sys/uio.h> -#endif -#include <sys/un.h> -#include <sysexits.h> -#include <syslog.h> -#include <unistd.h> - -#ifndef LINUX -#include <sys/queue.h> -#else -#include "queue.h" -#endif - -#ifndef PACKETSZ -#define PACKETSZ 512 -#endif - -static unsigned short csum (unsigned short *buf, int nwords) -{ - unsigned long sum; - for (sum = 0; nwords > 0; nwords--) - sum += *buf++; - sum = (sum >> 16) + (sum & 0xffff); - sum += (sum >> 16); - return ~sum; -} - -static int icmp_test() -{ - - int sockfd; - char datagram[400]; - struct sockaddr_in dest; - struct ip *iphdr=(struct ip *) datagram; -#if defined(OPENBSD) || defined(FREEBSD) ||defined(__APPLE__) || defined(__darwin__) - struct icmp *icmphdr=(struct icmp *)(iphdr +1); -#else - struct icmphdr *icmphdr=(struct icmphdr *)(iphdr +1); -#endif - char *buff=(char *)(icmphdr +1); - printf("\n----------------icmp() TEST----------------------------\n\n"); - - if((sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0) - { - perror("socket"); - exit(1); - } - - memset(datagram,0,400); - strcpy(buff,"entzwei"); - dest.sin_family=AF_INET; - dest.sin_addr.s_addr=inet_addr("192.168.1.33"); - - iphdr->ip_v=4; - iphdr->ip_hl=5; - iphdr->ip_len=sizeof(datagram); - iphdr->ip_id=(unsigned char)htonl(54321); - iphdr->ip_off=0; - iphdr->ip_ttl=225; - iphdr->ip_p=1; - iphdr->ip_sum=0; - iphdr->ip_tos=0; - iphdr->ip_src.s_addr=inet_addr("192.168.1.35"); - iphdr->ip_dst.s_addr=dest.sin_addr.s_addr; - iphdr->ip_sum=csum((unsigned short *)datagram,iphdr->ip_len >> 1); - -#if defined(OPENBSD) || defined(FREEBSD) ||defined(__APPLE__) || defined(__darwin__) - icmphdr->icmp_type=130; - icmphdr->icmp_code=0; - icmphdr->icmp_cksum=htons(0xc3b0); -#else - icmphdr->type=130; - icmphdr->code=0; - icmphdr->checksum=htons(0xc3b0); - icmphdr->un.echo.sequence=0; - icmphdr->un.echo.id=0; -#endif - int one=1; - int *val=&one; - if(setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,val,sizeof(one))<0) - printf("cannot set HDRINCL!\n"); - - - if(sendto(sockfd,datagram,35,0,(struct sockaddr *)&dest,sizeof(dest))<0) - { - perror("sendto"); - exit(1); - } - - return(0); -} - -static char *txtquery(const char *domain, unsigned int *ttl) -{ - unsigned char answer[PACKETSZ], *answend, *pt; - char *txt, host[128]; - int len, type, qtype; - unsigned int cttl, size, txtlen = 0; - - - *ttl = 0; - if(res_init() < 0) { - printf("^res_init failed\n"); - return NULL; - } - - printf("*Querying %s\n", domain); - - memset(answer, 0, PACKETSZ); - qtype = T_TXT; - if((len = res_query(domain, C_IN, qtype, answer, PACKETSZ)) < 0 || len > PACKETSZ) { - /* The DNS server in the SpeedTouch Alcatel 510 modem can't - * handle a TXT-query, but it can resolve an ANY-query to a - * TXT-record, so we try an ANY-query now. The thing we try - * to resolve normally only has a TXT-record anyway. - */ - memset(answer, 0, PACKETSZ); - qtype=T_ANY; - if((len = res_query(domain, C_IN, qtype, answer, PACKETSZ)) < 0) { - printf("^Can't query %s\n", domain); - return NULL; - } - } - - answend = answer + len; - pt = answer + sizeof(HEADER); - - if((len = dn_expand(answer, answend, pt, host, sizeof(host))) < 0) { - printf("^dn_expand failed\n"); - return NULL; - } - - pt += len; - if(pt > answend-4) { - printf("^Bad (too short) DNS reply\n"); - return NULL; - } - - GETSHORT(type, pt); - if(type != qtype) { - printf("^Broken DNS reply.\n"); - return NULL; - } - - pt += INT16SZ; /* class */ - size = 0; - do { /* recurse through CNAME rr's */ - pt += size; - if((len = dn_expand(answer, answend, pt, host, sizeof(host))) < 0) { - printf("^second dn_expand failed\n"); - return NULL; - } - printf("^Host: %s\n", host); - pt += len; - if(pt > answend-10) { - printf("^Bad (too short) DNS reply\n"); - return NULL; - } - GETSHORT(type, pt); - pt += INT16SZ; /* class */ - GETLONG(cttl, pt); - GETSHORT(size, pt); - if(pt + size < answer || pt + size > answend) { - printf("^DNS rr overflow\n"); - return NULL; - } - } while(type == T_CNAME); - - if(type != T_TXT) { - printf("^Not a TXT record\n"); - return NULL; - } - - if(!size || (txtlen = *pt) >= size || !txtlen) { - printf("^Broken TXT record (txtlen = %d, size = %d)\n", txtlen, size); - return NULL; - } - - if(!(txt = (char *) malloc(txtlen + 1))) - return NULL; - - memcpy(txt, pt+1, txtlen); - txt[txtlen] = 0; - *ttl = cttl; - - return txt; -} - -static int res_tests(char *ip, char *test) { - unsigned char dnsreply[1024]; - unsigned char host[128]; - int ret = 0, i; - char buf[16]; - struct sockaddr_in addr; - - memset( dnsreply, '\0', sizeof( dnsreply )); - - printf("\n---------------------- %s res_init() TEST----------------------\n\n", test); - if (res_init() == -1) { - printf("res_init failed\n"); - return -1; - } - - - addr.sin_family=AF_INET; - addr.sin_port=htons(53); - addr.sin_addr.s_addr=inet_addr(ip); - - for (i = 0; i < _res.nscount; i++) - memcpy(&_res.nsaddr_list[i], &addr, sizeof(struct sockaddr_in)); - - inet_ntop(AF_INET, &_res.nsaddr_list[0].sin_addr.s_addr, buf, sizeof(buf)); - printf("nameserver for test: %s\n", buf); - - /* Modifying _res directly doesn't work, so we have to use res_n* where available. - See: http://sourceware.org/ml/libc-help/2009-11/msg00013.html */ - printf("\n---------------------- %s res_query() TEST----------------------\n\n", test); - snprintf((char *)host, 127, "www.google.com"); -#if !defined(OPENBSD) && !defined(__APPLE__) && !defined(__darwin__) - ret = res_nquery(&_res, (char *) host, C_IN, T_TXT, dnsreply, sizeof( dnsreply )); -#else - ret = res_query((char *) host, C_IN, T_TXT, dnsreply, sizeof( dnsreply )); -#endif - printf("return code: %i\n", ret); - - printf("\n---------------------- %s res_search() TEST----------------------\n\n", test); - memset( dnsreply, '\0', sizeof( dnsreply )); -#if !defined(OPENBSD) && !defined(__APPLE__) && !defined(__darwin__) - ret = res_nsearch(&_res, (char *) host, C_IN, T_TXT, dnsreply, sizeof( dnsreply )); -#else - ret = res_search((char *) host, C_IN, T_TXT, dnsreply, sizeof( dnsreply )); -#endif - printf("return code: %i\n", ret); - - printf("\n--------------- %s res_querydomain() TEST----------------------\n\n", test); - memset( dnsreply, '\0', sizeof( dnsreply )); -#if !defined(OPENBSD) && !defined(__APPLE__) && !defined(__darwin__) - ret = res_nquerydomain(&_res, "www.google.com", "google.com", C_IN, T_TXT, dnsreply, sizeof( dnsreply )); -#else - ret = res_querydomain("www.google.com", "google.com", C_IN, T_TXT, dnsreply, sizeof( dnsreply )); -#endif - printf("return code: %i\n", ret); - - printf("\n---------------------- %s res_send() TEST----------------------\n\n", test); - memset( dnsreply, '\0', sizeof( dnsreply )); -#if !defined(OPENBSD) && !defined(__APPLE__) && !defined(__darwin__) - ret = res_nsend(&_res, host, 32, dnsreply, sizeof( dnsreply )); -#else - ret = res_send(host, 32, dnsreply, sizeof( dnsreply )); -#endif -printf("return code: %i\n", ret); - - return ret; -} - -static int res_internet_tests() { - char *ip = "8.8.8.8"; - char *test = "internet"; - return res_tests(ip, test); -} - -static int res_local_tests() { - char *ip = "192.168.1.1"; - char *test = "local"; - return res_tests(ip, test); -} - -static int udp_test() { - struct sockaddr_in addr; - char testtext[]="This message should be sent via udp\nThis is row number 2\nAnd then number three\n"; - int sock,ret,wb,flags=0; - char *ip = "6.6.6.6"; - - printf("\n----------------------UDP TEST----------------------\n\n"); - - addr.sin_family=AF_INET; - addr.sin_port=53; - addr.sin_addr.s_addr=inet_addr(ip); - - sock=socket(AF_INET,SOCK_DGRAM,0); - - struct iovec iov; - struct msghdr msg; - - iov.iov_base = (void *)testtext; - iov.iov_len = strlen(testtext); - - msg.msg_name = (struct sockaddr *)&addr; - msg.msg_namelen = sizeof(addr); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = NULL; - msg.msg_controllen = 0; - - printf("\n----------------------udp sendmsg() TEST-------------------\n\n"); - wb=0; - ret=sendmsg(sock, &msg, flags); - printf("sendmsg() returned ret=%d wb=%d\n",ret,wb); - - printf("\n----------------------udp sendto() TEST--------------------\n\n"); - wb=0; - ret=sendto(sock,testtext,strlen(testtext)+1,wb, (struct sockaddr*)&addr, sizeof(addr)); - ret=sendto(sock,"CiaoCiao",strlen("CiaoCiao")+1,wb, (struct sockaddr*)&addr, sizeof(addr)); - printf("sendto() returned ret=%d wb=%d\n",ret,wb); - - printf("\n----------------------udp connect() TEST-------------------\n\n"); - ret=connect(sock,(struct sockaddr*)&addr,sizeof(addr)); - printf("Connect returned ret=%d\n",ret); - - printf("\n----------------------udp send() TEST----------------------\n\n"); - wb=0; - ret=send(sock,testtext,strlen(testtext)+1,wb); - ret=send(sock,"CiaoCiao",strlen("CiaoCiao")+1,wb); - printf("Note: no interception by torsocks expected as send() requires a socket in a connected state.\n"); - printf("send() returned ret=%d wb=%d\n",ret,wb); - - return 0; -} - -static int gethostbyname_test() { - struct hostent *foo; - - printf("\n----------------------gethostbyname() TEST-----------------\n\n"); - - foo=gethostbyname("www.torproject.org"); - if (foo) { - int i; - for (i=0; foo->h_addr_list[i]; ++i) - printf("%s -> %s\n",foo->h_name,inet_ntoa(*(struct in_addr*)foo->h_addr_list[i])); -/* for (i=0; foo->h_aliases[i]; ++i) - printf(" also known as %s\n",foo->h_aliases[i]);*/ - } - return 0; -} - -static int gethostbyaddr_test() { - struct in_addr bar; - struct hostent *foo; - - printf("\n----------------------gethostbyaddr() TEST-----------------\n\n"); - - inet_aton("38.229.70.16", &bar); - foo=gethostbyaddr(&bar,4,AF_INET); - if (foo) { - int i; - for (i=0; foo->h_addr_list[i]; ++i) - printf("%s -> %s\n",foo->h_name,inet_ntoa(*(struct in_addr*)foo->h_addr_list[i])); - for (i=0; foo->h_aliases[i]; ++i) - printf(" also known as %s\n",foo->h_aliases[i]); - } - return 0; -} - -static int getaddrinfo_test() { - struct addrinfo hints; - struct addrinfo *result; - int s; - - printf("\n----------------------getaddrinfo() TEST-----------------\n\n"); - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_INET; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = SOCK_STREAM; /* Datagram socket */ - hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ - hints.ai_protocol = 0; /* Any protocol */ - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; - - s = getaddrinfo(NULL, "www.torproject.org", &hints, &result); - if (s != 0) { - printf("getaddrinfo: %s\n", gai_strerror(s)); - } - - return 0; -} - -/* Unavailable in glibc. */ -/* -static int getipnodebyname_test() { - int error; - - printf("\n----------------------getipnodebyname() TEST-----------------\n\n"); - - getipnodebyname("www.torproject.org", AF_INET, 0, &error); - if (error != 0) { - printf("getipnodebyname error: %i\n", error); - } - - return 0; -} -*/ - -static int connect_test(const char *name, const char *ip, int port) -{ - int sock; - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr(ip); - addr.sin_port = htons(port); - - printf("\n---------------------- %s connect() TEST----------------------\n\n", name); - - if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) - return 1; - - if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) - return 1; - - return 0; -} - -static int connect_local_test() -{ - const char *ip = "192.168.1.1"; - const char *name = "local"; - int port = 80; - return connect_test(name, ip, port); -} - -static int connect_internet_test() -{ - const char *ip = "8.8.8.8"; - int port = 53; - const char *name = "internet"; - return connect_test(name, ip, port); -} - -int main() { - - getaddrinfo_test(); - udp_test(); - gethostbyaddr_test(); - gethostbyname_test(); - connect_local_test(); - connect_internet_test(); - res_internet_tests(); - res_local_tests(); - icmp_test(); - - return 0; -} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..97b7b2b --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = utils diff --git a/tests/utils/Makefile.am b/tests/utils/Makefile.am new file mode 100644 index 0000000..feae65a --- /dev/null +++ b/tests/utils/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = tap diff --git a/tests/utils/tap/Makefile.am b/tests/utils/tap/Makefile.am new file mode 100644 index 0000000..d3b5b58 --- /dev/null +++ b/tests/utils/tap/Makefile.am @@ -0,0 +1,4 @@ +noinst_LTLIBRARIES = libtap.la +libtap_la_SOURCES = tap.c tap.h +dist_noinst_SCRIPTS = tap.sh +EXTRA_DIST = tap.sh diff --git a/tests/utils/tap/tap.c b/tests/utils/tap/tap.c new file mode 100644 index 0000000..8bf72f6 --- /dev/null +++ b/tests/utils/tap/tap.c @@ -0,0 +1,433 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _GNU_SOURCE +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include "tap.h" + +static int no_plan = 0; +static int skip_all = 0; +static int have_plan = 0; +static unsigned int test_count = 0; /* Number of tests that have been run */ +static unsigned int e_tests = 0; /* Expected number of tests to run */ +static unsigned int failures = 0; /* Number of tests that failed */ +static char *todo_msg = NULL; +static char *todo_msg_fixed = "libtap malloc issue"; +static int todo = 0; +static int test_died = 0; + +/* Encapsulate the pthread code in a conditional. In the absence of + libpthread the code does nothing */ +#ifdef HAVE_LIBPTHREAD +#include <pthread.h> +static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER; +# define LOCK pthread_mutex_lock(&M); +# define UNLOCK pthread_mutex_unlock(&M); +#else +# define LOCK +# define UNLOCK +#endif + +static void _expected_tests(unsigned int); +static void _tap_init(void); +static void _cleanup(void); + +/* + * Generate a test result. + * + * ok -- boolean, indicates whether or not the test passed. + * test_name -- the name of the test, may be NULL + * test_comment -- a comment to print afterwards, may be NULL + */ +unsigned int +_gen_result(int ok, const char *func, char *file, unsigned int line, + char *test_name, ...) +{ + va_list ap; + char *local_test_name = NULL; + char *c; + int name_is_digits; + + LOCK; + + test_count++; + + /* Start by taking the test name and performing any printf() + expansions on it */ + if(test_name != NULL) { + va_start(ap, test_name); + if (vasprintf(&local_test_name, test_name, ap) == -1) { + local_test_name = NULL; + } + va_end(ap); + + /* Make sure the test name contains more than digits + and spaces. Emit an error message and exit if it + does */ + if(local_test_name) { + name_is_digits = 1; + for(c = local_test_name; *c != '\0'; c++) { + if(!isdigit(*c) && !isspace(*c)) { + name_is_digits = 0; + break; + } + } + + if(name_is_digits) { + diag(" You named your test '%s'. You shouldn't use numbers for your test names.", local_test_name); + diag(" Very confusing."); + } + } + } + + if(!ok) { + printf("not "); + failures++; + } + + printf("ok %d", test_count); + + if(test_name != NULL) { + printf(" - "); + + /* Print the test name, escaping any '#' characters it + might contain */ + if(local_test_name != NULL) { + flockfile(stdout); + for(c = local_test_name; *c != '\0'; c++) { + if(*c == '#') + fputc('\', stdout); + fputc((int)*c, stdout); + } + funlockfile(stdout); + } else { /* vasprintf() failed, use a fixed message */ + printf("%s", todo_msg_fixed); + } + } + + /* If we're in a todo_start() block then flag the test as being + TODO. todo_msg should contain the message to print at this + point. If it's NULL then asprintf() failed, and we should + use the fixed message. + + This is not counted as a failure, so decrement the counter if + the test failed. */ + if(todo) { + printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed); + if(!ok) + failures--; + } + + printf("\n"); + + if(!ok) { + if(getenv("HARNESS_ACTIVE") != NULL) + fputs("\n", stderr); + + diag(" Failed %stest (%s:%s() at line %d)", + todo ? "(TODO) " : "", file, func, line); + } + free(local_test_name); + + UNLOCK; + + /* We only care (when testing) that ok is positive, but here we + specifically only want to return 1 or 0 */ + return ok ? 1 : 0; +} + +/* + * Initialise the TAP library. Will only do so once, however many times it's + * called. + */ +void +_tap_init(void) +{ + static int run_once = 0; + + if(!run_once) { + atexit(_cleanup); + + /* stdout needs to be unbuffered so that the output appears + in the same place relative to stderr output as it does + with Test::Harness */ + setbuf(stdout, 0); + run_once = 1; + } +} + +/* + * Note that there's no plan. + */ +int +plan_no_plan(void) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + have_plan = 1; + no_plan = 1; + + UNLOCK; + + return 1; +} + +/* + * Note that the plan is to skip all tests + */ +int +plan_skip_all(char *reason) +{ + + LOCK; + + _tap_init(); + + skip_all = 1; + + printf("1..0"); + + if(reason != NULL) + printf(" # Skip %s", reason); + + printf("\n"); + + UNLOCK; + + exit(0); +} + +/* + * Note the number of tests that will be run. + */ +int +plan_tests(unsigned int tests) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + if(tests == 0) { + fprintf(stderr, "You said to run 0 tests! You've got to run something.\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + have_plan = 1; + + _expected_tests(tests); + + UNLOCK; + + return e_tests; +} + +unsigned int +diag(char *fmt, ...) +{ + va_list ap; + + fputs("# ", stderr); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputs("\n", stderr); + + return 0; +} + +void +_expected_tests(unsigned int tests) +{ + + printf("1..%d\n", tests); + e_tests = tests; +} + +int +skip(unsigned int n, char *fmt, ...) +{ + va_list ap; + char *skip_msg = NULL; + + LOCK; + + va_start(ap, fmt); + if (asprintf(&skip_msg, fmt, ap) == -1) { + skip_msg = NULL; + } + va_end(ap); + + while(n-- > 0) { + test_count++; + printf("ok %d # skip %s\n", test_count, + skip_msg != NULL ? + skip_msg : "libtap():malloc() failed"); + } + + free(skip_msg); + + UNLOCK; + + return 1; +} + +void +todo_start(char *fmt, ...) +{ + va_list ap; + + LOCK; + + va_start(ap, fmt); + if (vasprintf(&todo_msg, fmt, ap) == -1) { + todo_msg = NULL; + } + va_end(ap); + + todo = 1; + + UNLOCK; +} + +void +todo_end(void) +{ + + LOCK; + + todo = 0; + free(todo_msg); + + UNLOCK; +} + +int +exit_status(void) +{ + int r; + + LOCK; + + /* If there's no plan, just return the number of failures */ + if(no_plan || !have_plan) { + UNLOCK; + return failures; + } + + /* Ran too many tests? Return the number of tests that were run + that shouldn't have been */ + if(e_tests < test_count) { + r = test_count - e_tests; + UNLOCK; + return r; + } + + /* Return the number of tests that failed + the number of tests + that weren't run */ + r = failures + e_tests - test_count; + UNLOCK; + + return r; +} + +/* + * Cleanup at the end of the run, produce any final output that might be + * required. + */ +void +_cleanup(void) +{ + + LOCK; + + /* If plan_no_plan() wasn't called, and we don't have a plan, + and we're not skipping everything, then something happened + before we could produce any output */ + if(!no_plan && !have_plan && !skip_all) { + diag("Looks like your test died before it could output anything."); + UNLOCK; + return; + } + + if(test_died) { + diag("Looks like your test died just after %d.", test_count); + UNLOCK; + return; + } + + + /* No plan provided, but now we know how many tests were run, and can + print the header at the end */ + if(!skip_all && (no_plan || !have_plan)) { + printf("1..%d\n", test_count); + } + + if((have_plan && !no_plan) && e_tests < test_count) { + diag("Looks like you planned %d %s but ran %d extra.", + e_tests, e_tests == 1 ? "test" : "tests", test_count - e_tests); + UNLOCK; + return; + } + + if((have_plan || !no_plan) && e_tests > test_count) { + diag("Looks like you planned %d %s but only ran %d.", + e_tests, e_tests == 1 ? "test" : "tests", test_count); + UNLOCK; + return; + } + + if(failures) + diag("Looks like you failed %d %s of %d.", + failures, failures == 1 ? "test" : "tests", test_count); + + UNLOCK; +} diff --git a/tests/utils/tap/tap.h b/tests/utils/tap/tap.h new file mode 100644 index 0000000..0f05943 --- /dev/null +++ b/tests/utils/tap/tap.h @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting + and requires the caller to add the final comma if they've ommitted + the optional arguments */ +#ifdef __GNUC__ +# define ok(e, test, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + test, ## __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + test, ## __VA_ARGS__)) + +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +# define pass(test, ...) ok(1, test, ## __VA_ARGS__); +# define fail(test, ...) ok(0, test, ## __VA_ARGS__); + +# define skip_start(test, n, fmt, ...) \ + do { \ + if((test)) { \ + skip(n, fmt, ## __VA_ARGS__); \ + continue; \ + } +#elif __STDC_VERSION__ >= 199901L /* __GNUC__ */ +# define ok(e, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + __VA_ARGS__)) + +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +# define pass(...) ok(1, __VA_ARGS__); +# define fail(...) ok(0, __VA_ARGS__); + +# define skip_start(test, n, ...) \ + do { \ + if((test)) { \ + skip(n, __VA_ARGS__); \ + continue; \ + } +#else /* __STDC_VERSION__ */ +# error "Needs gcc or C99 compiler for variadic macros." +#endif /* __STDC_VERSION__ */ + +#define skip_end() } while(0); + +unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...); + +int plan_no_plan(void); +int plan_skip_all(char *); +int plan_tests(unsigned int); + +unsigned int diag(char *, ...); + +int skip(unsigned int, char *, ...); + +void todo_start(char *, ...); +void todo_end(void); + +int exit_status(void); diff --git a/tests/utils/tap/tap.sh b/tests/utils/tap/tap.sh new file mode 100755 index 0000000..24ac1aa --- /dev/null +++ b/tests/utils/tap/tap.sh @@ -0,0 +1,456 @@ +#!/bin/bash +# +# Copyright 2010 Patrick LeBoutillier patrick.leboutillier@gmail.com +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + + +_version='1.01' + +_plan_set=0 +_no_plan=0 +_skip_all=0 +_test_died=0 +_expected_tests=0 +_executed_tests=0 +_failed_tests=0 +TODO= + + +usage(){ + cat <<'USAGE' +tap-functions: A TAP-producing BASH library + +PLAN: + plan_no_plan + plan_skip_all [REASON] + plan_tests NB_TESTS + +TEST: + ok RESULT [NAME] + okx COMMAND + is RESULT EXPECTED [NAME] + isnt RESULT EXPECTED [NAME] + like RESULT PATTERN [NAME] + unlike RESULT PATTERN [NAME] + pass [NAME] + fail [NAME] + +SKIP: + skip [CONDITION] [REASON] [NB_TESTS=1] + + skip $feature_not_present "feature not present" 2 || { + is $a "a" + is $b "b" + } + +TODO: + Specify TODO mode by setting $TODO: + TODO="not implemented yet" + ok $result "some not implemented test" + unset TODO + +OTHER: + diag MSG + +EXAMPLE: + #!/bin/bash + + . tap-functions + + plan_tests 7 + + me=$USER + is $USER $me "I am myself" + like $HOME $me "My home is mine" + like "`id`" $me "My id matches myself" + + /bin/ls $HOME 1>&2 + ok $? "/bin/ls $HOME" + # Same thing using okx shortcut + okx /bin/ls $HOME + + [[ "`id -u`" != "0" ]] + i_am_not_root=$? + skip $i_am_not_root "Must be root" || { + okx ls /root + } + + TODO="figure out how to become root..." + okx [ "$HOME" == "/root" ] + unset TODO +USAGE + exit +} + +opt= +set_u= +while getopts ":sx" opt ; do + case $_opt in + u) set_u=1 ;; + *) usage ;; + esac +done +shift $(( OPTIND - 1 )) +# Don't allow uninitialized variables if requested +[[ -n "$set_u" ]] && set -u +unset opt set_u + +# Used to call _cleanup on shell exit +trap _exit EXIT + + +plan_no_plan(){ + (( _plan_set != 0 )) && "You tried to plan twice!" + + _plan_set=1 + _no_plan=1 + + return 0 +} + + +plan_skip_all(){ + local reason=${1:-''} + + (( _plan_set != 0 )) && _die "You tried to plan twice!" + + _print_plan 0 "Skip $reason" + + _skip_all=1 + _plan_set=1 + _exit 0 + + return 0 +} + +plan_tests(){ + local tests=${1:?} + + (( _plan_set != 0 )) && _die "You tried to plan twice!" + (( tests == 0 )) && _die "You said to run 0 tests! You've got to run something." + + _print_plan $tests + _expected_tests=$tests + _plan_set=1 + + return $tests +} + + +_print_plan(){ + local tests=${1:?} + local directive=${2:-''} + + echo -n "1..$tests" + [[ -n "$directive" ]] && echo -n " # $directive" + echo +} + + +pass(){ + local name=$1 + ok 0 "$name" +} + + +fail(){ + local name=$1 + ok 1 "$name" +} + +# This is the workhorse method that actually +# prints the tests result. +ok(){ + local result=${1:?} + local name=${2:-''} + + (( _plan_set == 0 )) && _die "You tried to run a test without a plan! Gotta have a plan." + + _executed_tests=$(( $_executed_tests + 1 )) + + if [[ -n "$name" ]] ; then + if _matches "$name" "^[0-9]+$" ; then + diag " You named your test '$name'. You shouldn't use numbers for your test names." + diag " Very confusing." + fi + fi + + if (( result != 0 )) ; then + echo -n "not " + _failed_tests=$(( _failed_tests + 1 )) + fi + echo -n "ok $_executed_tests" + + if [[ -n "$name" ]] ; then + local ename=${name//#/\#} + echo -n " - $ename" + fi + + if [[ -n "$TODO" ]] ; then + echo -n " # TODO $TODO" ; + if (( result != 0 )) ; then + _failed_tests=$(( _failed_tests - 1 )) + fi + fi + + echo + if (( result != 0 )) ; then + local file='tap-functions' + local func= + local line= + + local i=0 + local bt=$(caller $i) + while _matches "$bt" "tap-functions$" ; do + i=$(( $i + 1 )) + bt=$(caller $i) + done + local backtrace= + eval $(caller $i | (read line func file ; echo "backtrace="$file:$func() at line $line."")) + + local t= + [[ -n "$TODO" ]] && t="(TODO) " + + if [[ -n "$name" ]] ; then + diag " Failed ${t}test '$name'" + diag " in $backtrace" + else + diag " Failed ${t}test in $backtrace" + fi + fi + + return $result +} + + +okx(){ + local command="$@" + + local line= + diag "Output of '$command':" + "$@" | while read line ; do + diag "$line" + done + ok ${PIPESTATUS[0]} "$command" +} + + +_equals(){ + local result=${1:?} + local expected=${2:?} + + if [[ "$result" == "$expected" ]] ; then + return 0 + else + return 1 + fi +} + + +# Thanks to Aaron Kangas for the patch to allow regexp matching +# under bash < 3. + _bash_major_version=${BASH_VERSION%%.*} +_matches(){ + local result=${1:?} + local pattern=${2:?} + + if [[ -z "$result" || -z "$pattern" ]] ; then + return 1 + else + if (( _bash_major_version >= 3 )) ; then + [[ "$result" =~ "$pattern" ]] + else + echo "$result" | egrep -q "$pattern" + fi + fi +} + + +_is_diag(){ + local result=${1:?} + local expected=${2:?} + + diag " got: '$result'" + diag " expected: '$expected'" +} + + +is(){ + local result=${1:?} + local expected=${2:?} + local name=${3:-''} + + _equals "$result" "$expected" + (( $? == 0 )) + ok $? "$name" + local r=$? + (( r != 0 )) && _is_diag "$result" "$expected" + return $r +} + + +isnt(){ + local result=${1:?} + local expected=${2:?} + local name=${3:-''} + + _equals "$result" "$expected" + (( $? != 0 )) + ok $? "$name" + local r=$? + (( r != 0 )) && _is_diag "$result" "$expected" + return $r +} + + +like(){ + local result=${1:?} + local pattern=${2:?} + local name=${3:-''} + + _matches "$result" "$pattern" + (( $? == 0 )) + ok $? "$name" + local r=$? + (( r != 0 )) && diag " '$result' doesn't match '$pattern'" + return $r +} + + +unlike(){ + local result=${1:?} + local pattern=${2:?} + local name=${3:-''} + + _matches "$result" "$pattern" + (( $? != 0 )) + ok $? "$name" + local r=$? + (( r != 0 )) && diag " '$result' matches '$pattern'" + return $r +} + + +skip(){ + local condition=${1:?} + local reason=${2:-''} + local n=${3:-1} + + if (( condition == 0 )) ; then + local i= + for (( i=0 ; i<$n ; i++ )) ; do + _executed_tests=$(( _executed_tests + 1 )) + echo "ok $_executed_tests # skip: $reason" + done + return 0 + else + return + fi +} + + +diag(){ + local msg=${1:?} + + if [[ -n "$msg" ]] ; then + echo "# $msg" + fi + + return 1 +} + + +_die(){ + local reason=${1:-'<unspecified error>'} + + echo "$reason" >&2 + _test_died=1 + _exit 255 +} + + +BAIL_OUT(){ + local reason=${1:-''} + + echo "Bail out! $reason" >&2 + _exit 255 +} + + +_cleanup(){ + local rc=0 + + if (( _plan_set == 0 )) ; then + diag "Looks like your test died before it could output anything." + return $rc + fi + + if (( _test_died != 0 )) ; then + diag "Looks like your test died just after $_executed_tests." + return $rc + fi + + if (( _skip_all == 0 && _no_plan != 0 )) ; then + _print_plan $_executed_tests + fi + + local s= + if (( _no_plan == 0 && _expected_tests < _executed_tests )) ; then + s= ; (( _expected_tests > 1 )) && s=s + local extra=$(( _executed_tests - _expected_tests )) + diag "Looks like you planned $_expected_tests test$s but ran $extra extra." + rc=1 ; + fi + + if (( _no_plan == 0 && _expected_tests > _executed_tests )) ; then + s= ; (( _expected_tests > 1 )) && s=s + diag "Looks like you planned $_expected_tests test$s but only ran $_executed_tests." + fi + + if (( _failed_tests > 0 )) ; then + s= ; (( _failed_tests > 1 )) && s=s + diag "Looks like you failed $_failed_tests test$s of $_executed_tests." + fi + + return $rc +} + + +_exit_status(){ + if (( _no_plan != 0 || _plan_set == 0 )) ; then + return $_failed_tests + fi + + if (( _expected_tests < _executed_tests )) ; then + return $(( _executed_tests - _expected_tests )) + fi + + return $(( _failed_tests + ( _expected_tests - _executed_tests ))) +} + + +_exit(){ + local rc=${1:-''} + if [[ -z "$rc" ]] ; then + _exit_status + rc=$? + fi + + _cleanup + local alt_rc=$? + (( alt_rc != 0 )) && rc=$alt_rc + trap - EXIT + exit $rc +}
tor-commits@lists.torproject.org