xref: /5.5.2/moxi/src/testapp.c (revision d0366df5)
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2#undef NDEBUG
3#include "src/config.h"
4#include <sys/types.h>
5#include <sys/socket.h>
6#include <netdb.h>
7#include <arpa/inet.h>
8#include <netinet/in.h>
9#include <netinet/tcp.h>
10#include <signal.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <errno.h>
14#include <platform/cbassert.h>
15#include <string.h>
16#include <inttypes.h>
17#include <stdbool.h>
18#include <unistd.h>
19#include <netinet/in.h>
20
21#include "protocol_binary.h"
22#include "cache.h"
23#include "util.h"
24
25#define TMP_TEMPLATE "/tmp/test_file.XXXXXXX"
26
27enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL };
28
29static enum test_return cache_create_test(void)
30{
31    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
32                                  NULL, NULL);
33    cb_assert(cache != NULL);
34    cache_destroy(cache);
35    return TEST_PASS;
36}
37
38const uint64_t constructor_pattern = 0xdeadcafebabebeef;
39
40static int cache_constructor(void *buffer, void *notused1, int notused2) {
41    uint64_t *ptr = buffer;
42    *ptr = constructor_pattern;
43
44    (void)notused1;
45    (void)notused2;
46
47    return 0;
48}
49
50static enum test_return cache_constructor_test(void)
51{
52    uint64_t *ptr;
53    uint64_t pattern;
54    cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
55                                  cache_constructor, NULL);
56    cb_assert(cache != NULL);
57    ptr = cache_alloc(cache);
58    pattern = *ptr;
59    cache_free(cache, ptr);
60    cache_destroy(cache);
61    return (pattern == constructor_pattern) ? TEST_PASS : TEST_FAIL;
62}
63
64static int cache_fail_constructor(void *buffer, void *notused1, int notused2) {
65    (void)buffer;
66    (void)notused1;
67    (void)notused2;
68    return 1;
69}
70
71static enum test_return cache_fail_constructor_test(void)
72{
73    enum test_return ret = TEST_PASS;
74
75    cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
76                                  cache_fail_constructor, NULL);
77    uint64_t *ptr;
78
79    cb_assert(cache != NULL);
80    ptr = cache_alloc(cache);
81    if (ptr != NULL) {
82        ret = TEST_FAIL;
83    }
84    cache_destroy(cache);
85    return ret;
86}
87
88static void *destruct_data = 0;
89
90static void cache_destructor(void *buffer, void *notused) {
91    (void)notused;
92    destruct_data = buffer;
93}
94
95static enum test_return cache_destructor_test(void)
96{
97    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
98                                  NULL, cache_destructor);
99    char *ptr;
100
101    cb_assert(cache != NULL);
102    ptr = cache_alloc(cache);
103    cache_free(cache, ptr);
104    cache_destroy(cache);
105
106    return (ptr == destruct_data) ? TEST_PASS : TEST_FAIL;
107}
108
109static enum test_return cache_reuse_test(void)
110{
111    int ii;
112    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
113                                  NULL, NULL);
114    char *ptr = cache_alloc(cache);
115    cache_free(cache, ptr);
116    for (ii = 0; ii < 100; ++ii) {
117        char *p = cache_alloc(cache);
118        cb_assert(p == ptr);
119        cache_free(cache, ptr);
120    }
121    cache_destroy(cache);
122    return TEST_PASS;
123}
124
125static enum test_return cache_redzone_test(void)
126{
127#ifndef HAVE_UMEM_H
128    cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
129                                  NULL, NULL);
130
131    char *p;
132    char old;
133    /* Ignore SIGABORT */
134    struct sigaction old_action;
135    struct sigaction action;
136    memset(&action, 0, sizeof(action));
137    action.sa_handler = SIG_IGN;
138
139    sigemptyset(&action.sa_mask);
140    sigaction(SIGABRT, &action, &old_action);
141
142    /* check memory debug.. */
143    p = cache_alloc(cache);
144    old = *(p - 1);
145    *(p - 1) = 0;
146    cache_free(cache, p);
147    cb_assert(cache_error == -1);
148    *(p - 1) = old;
149
150    p[sizeof(uint32_t)] = 0;
151    cache_free(cache, p);
152    cb_assert(cache_error == 1);
153
154    /* restore signal handler */
155    sigaction(SIGABRT, &old_action, NULL);
156
157    cache_destroy(cache);
158
159    return TEST_PASS;
160#else
161    return TEST_SKIP;
162#endif
163}
164
165static enum test_return test_safe_strtoul(void) {
166    uint32_t val;
167    cb_assert(safe_strtoul("123", &val));
168    cb_assert(val == 123);
169    cb_assert(safe_strtoul("+123", &val));
170    cb_assert(val == 123);
171    cb_assert(!safe_strtoul("", &val));  /* empty */
172    cb_assert(!safe_strtoul("123BOGUS", &val));  /* non-numeric */
173    /* Not sure what it does, but this works with ICC :/
174       cb_assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range
175    */
176
177    /* extremes: */
178    cb_assert(safe_strtoul("4294967295", &val)); /* 2**32 - 1 */
179    cb_assert(val == 4294967295L);
180    /* This actually works on 64-bit ubuntu
181       cb_assert(!safe_strtoul("4294967296", &val)); // 2**32
182    */
183    cb_assert(!safe_strtoul("-1", &val));  /* negative */
184    return TEST_PASS;
185}
186
187
188static enum test_return test_safe_strtoull(void) {
189    uint64_t val;
190    cb_assert(safe_strtoull("123", &val));
191    cb_assert(val == 123);
192    cb_assert(safe_strtoull("+123", &val));
193    cb_assert(val == 123);
194    cb_assert(!safe_strtoull("", &val));  /* empty */
195    cb_assert(!safe_strtoull("123BOGUS", &val));  /* non-numeric */
196    cb_assert(!safe_strtoull("92837498237498237498029383", &val)); /* out of range */
197
198    /* extremes: */
199    cb_assert(safe_strtoull("18446744073709551615", &val)); /* 2**64 - 1 */
200    cb_assert(val == 18446744073709551615ULL);
201    cb_assert(!safe_strtoull("18446744073709551616", &val)); /* 2**64 */
202    cb_assert(!safe_strtoull("-1", &val));  /* negative */
203    return TEST_PASS;
204}
205
206static enum test_return test_safe_strtoll(void) {
207    int64_t val;
208    cb_assert(safe_strtoll("123", &val));
209    cb_assert(val == 123);
210    cb_assert(safe_strtoll("+123", &val));
211    cb_assert(val == 123);
212    cb_assert(safe_strtoll("-123", &val));
213    cb_assert(val == -123);
214    cb_assert(!safe_strtoll("", &val));  /* empty */
215    cb_assert(!safe_strtoll("123BOGUS", &val));  /* non-numeric */
216    cb_assert(!safe_strtoll("92837498237498237498029383", &val)); /* out of range */
217
218    /* extremes: */
219    cb_assert(!safe_strtoll("18446744073709551615", &val)); /* 2**64 - 1 */
220    cb_assert(safe_strtoll("9223372036854775807", &val)); /* 2**63 - 1 */
221    cb_assert(val == 9223372036854775807LL);
222    /*
223      cb_assert(safe_strtoll("-9223372036854775808", &val)); // -2**63
224      cb_assert(val == -9223372036854775808LL);
225    */
226    cb_assert(!safe_strtoll("-9223372036854775809", &val)); /* -2**63 - 1 */
227
228    /* We'll allow space to terminate the string.  And leading space. */
229    cb_assert(safe_strtoll(" 123 foo", &val));
230    cb_assert(val == 123);
231    return TEST_PASS;
232}
233
234static enum test_return test_safe_strtol(void) {
235    int32_t val;
236    cb_assert(safe_strtol("123", &val));
237    cb_assert(val == 123);
238    cb_assert(safe_strtol("+123", &val));
239    cb_assert(val == 123);
240    cb_assert(safe_strtol("-123", &val));
241    cb_assert(val == -123);
242    cb_assert(!safe_strtol("", &val));  /* empty */
243    cb_assert(!safe_strtol("123BOGUS", &val));  /* non-numeric */
244    cb_assert(!safe_strtol("92837498237498237498029383", &val)); /* out of range */
245
246    /* extremes: */
247    /* This actually works on 64-bit ubuntu
248       cb_assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0)
249    */
250    cb_assert(safe_strtol("2147483647", &val)); /* (- (expt 2.0 31) 1) */
251    cb_assert(val == 2147483647L);
252    /* This actually works on 64-bit ubuntu
253       cb_assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1)
254    */
255
256    /* We'll allow space to terminate the string.  And leading space. */
257    cb_assert(safe_strtol(" 123 foo", &val));
258    cb_assert(val == 123);
259    return TEST_PASS;
260}
261
262/**
263 * Function to start the server and let it listen on a random port
264 *
265 * @param port_out where to store the TCP port number the server is
266 *                 listening on
267 * @param is_daemon set to true if you want to run the memcached server
268 *               as a daemon process
269 * @return the pid of the memcached server
270 */
271static pid_t start_server(in_port_t *port_out, bool is_daemon) {
272    pid_t pid;
273    FILE *fp;
274    char buffer[80];
275    char environment[80];
276    char pid_file[80];
277    char *filename;
278
279    snprintf(environment, sizeof(environment),
280             "MEMCACHED_PORT_FILENAME=/tmp/ports.%lu", (long)getpid());
281    filename = environment + strlen("MEMCACHED_PORT_FILENAME=");
282    snprintf(pid_file, sizeof(pid_file), "/tmp/pid.%lu", (long)getpid());
283
284    remove(filename);
285    remove(pid_file);
286
287    pid = fork();
288    cb_assert(pid != -1);
289
290    if (pid == 0) {
291        /* Child */
292        char *argv[20];
293        int arg = 0;
294        putenv(environment);
295#ifdef __sun
296        putenv("LD_PRELOAD=watchmalloc.so.1");
297        putenv("MALLOC_DEBUG=WATCH");
298#endif
299
300        if (!is_daemon) {
301            argv[arg++] = "./timedrun";
302            argv[arg++] = "15";
303        }
304        argv[arg++] = "./memcached-debug";
305        argv[arg++] = "-p";
306        argv[arg++] = "-1";
307        argv[arg++] = "-U";
308        argv[arg++] = "0";
309        /* Handle rpmbuild and the like doing this as root */
310        if (getuid() == 0) {
311            argv[arg++] = "-u";
312            argv[arg++] = "root";
313        }
314        if (is_daemon) {
315            argv[arg++] = "-d";
316            argv[arg++] = "-P";
317            argv[arg++] = pid_file;
318        }
319        argv[arg++] = NULL;
320        cb_assert(execv(argv[0], argv) != -1);
321    }
322
323    /* Yeah just let us "busy-wait" for the file to be created ;-) */
324    while (access(filename, F_OK) == -1) {
325        usleep(10);
326    }
327
328    fp = fopen(filename, "r");
329    if (fp == NULL) {
330        fprintf(stderr, "Failed to open the file containing port numbers: %s\n",
331                strerror(errno));
332        cb_assert(false);
333    }
334
335    *port_out = (in_port_t)-1;
336    while ((fgets(buffer, sizeof(buffer), fp)) != NULL) {
337        if (strncmp(buffer, "TCP INET: ", 10) == 0) {
338            int32_t val;
339            cb_assert(safe_strtol(buffer + 10, &val));
340            *port_out = (in_port_t)val;
341        }
342    }
343
344    fclose(fp);
345    cb_assert(remove(filename) == 0);
346
347    if (is_daemon) {
348        int32_t val;
349        /* loop and wait for the pid file.. There is a potential race
350         * condition that the server just created the file but isn't
351         * finished writing the content, but I'll take the chance....
352         */
353        while (access(pid_file, F_OK) == -1) {
354            usleep(10);
355        }
356
357        fp = fopen(pid_file, "r");
358        if (fp == NULL) {
359            fprintf(stderr, "Failed to open pid file: %s\n",
360                    strerror(errno));
361            cb_assert(false);
362        }
363        cb_assert(fgets(buffer, sizeof(buffer), fp) != NULL);
364        fclose(fp);
365
366        cb_assert(safe_strtol(buffer, &val));
367        pid = (pid_t)val;
368    }
369
370    return pid;
371}
372
373static enum test_return test_issue_44(void) {
374    in_port_t port;
375    pid_t pid = start_server(&port, true);
376    cb_assert(kill(pid, SIGHUP) == 0);
377    sleep(1);
378    cb_assert(kill(pid, SIGTERM) == 0);
379
380    return TEST_PASS;
381}
382
383/*
384 * static struct addrinfo *lookuphost(const char *hostname, in_port_t port)
385 * {
386 *     struct addrinfo *ai = 0;
387 *     struct addrinfo hints = { .ai_family = AF_UNSPEC,
388 *                               .ai_protocol = IPPROTO_TCP,
389 *                               .ai_socktype = SOCK_STREAM };
390 *     char service[NI_MAXSERV];
391 *     int error;
392 *
393 *     (void)snprintf(service, NI_MAXSERV, "%d", port);
394 *     if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) {
395 *        if (error != EAI_SYSTEM) {
396 *           fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
397 *        } else {
398 *           perror("getaddrinfo()");
399 *        }
400 *     }
401 *
402 *     return ai;
403 * }
404 */
405
406/*
407 * static int connect_server(const char *hostname, in_port_t port)
408 * {
409 *     struct addrinfo *ai = lookuphost(hostname, port);
410 *     int sock = -1;
411 *     if (ai != NULL) {
412 *        if ((sock = socket(ai->ai_family, ai->ai_socktype,
413 *                           ai->ai_protocol)) != -1) {
414 *           if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
415 *              fprintf(stderr, "Failed to connect socket: %s\n",
416 *                      strerror(errno));
417 *              close(sock);
418 *              sock = -1;
419 *           }
420 *        } else {
421 *           fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
422 *        }
423 *
424 *        freeaddrinfo(ai);
425 *     }
426 *     return sock;
427 * }
428 */
429
430static enum test_return test_vperror(void) {
431    int rv = 0;
432    int oldstderr = dup(STDERR_FILENO);
433    char tmpl[sizeof(TMP_TEMPLATE)+1];
434    int newfile;
435    char buf[80] = { 0 };
436    FILE *efile;
437    char *prv;
438    char expected[80] = { 0 };
439
440    strncpy(tmpl, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1);
441
442    newfile = mkstemp(tmpl);
443    cb_assert(newfile > 0);
444    rv = dup2(newfile, STDERR_FILENO);
445    cb_assert(rv == STDERR_FILENO);
446    rv = close(newfile);
447    cb_assert(rv == 0);
448
449    errno = EIO;
450    vperror("Old McDonald had a farm.  %s", "EI EIO");
451
452    /* Restore stderr */
453    rv = dup2(oldstderr, STDERR_FILENO);
454    cb_assert(rv == STDERR_FILENO);
455
456
457    /* Go read the file */
458    efile = fopen(tmpl, "r");
459    cb_assert(efile);
460    prv = fgets(buf, sizeof(buf), efile);
461    cb_assert(prv);
462    fclose(efile);
463
464    unlink(tmpl);
465
466    snprintf(expected, sizeof(expected),
467             "Old McDonald had a farm.  EI EIO: %s\n", strerror(EIO));
468
469    /*
470    fprintf(stderr,
471            "\nExpected:  ``%s''"
472            "\nGot:       ``%s''\n", expected, buf);
473    */
474
475    return strcmp(expected, buf) == 0 ? TEST_PASS : TEST_FAIL;
476}
477
478
479static enum test_return test_issue_72(void) {
480    /* SKIP: moxi doesn't handle MEMCACHED_PORT_FILENAME now. */
481    return TEST_SKIP;
482}
483
484typedef enum test_return (*TEST_FUNC)(void);
485struct testcase {
486    const char *description;
487    TEST_FUNC function;
488};
489
490struct testcase testcases[] = {
491    { "cache_create", cache_create_test },
492    { "cache_constructor", cache_constructor_test },
493    { "cache_constructor_fail", cache_fail_constructor_test },
494    { "cache_destructor", cache_destructor_test },
495    { "cache_reuse", cache_reuse_test },
496    { "cache_redzone", cache_redzone_test },
497    { "issue_44", test_issue_44 },
498    { "issue_72", test_issue_72 },
499    { "vperror", test_vperror },
500    { "safestrtoul", test_safe_strtoul },
501    { "safestrtoull", test_safe_strtoull },
502    { "safestrtoll", test_safe_strtoll },
503    { "safestrtol", test_safe_strtol },
504    { NULL, NULL }
505};
506
507int main(void)
508{
509    int exitcode = 0;
510    int ii = 0, num_cases = 0;
511
512    for (num_cases = 0; testcases[num_cases].description; num_cases++) {
513        /* Just counting */
514    }
515
516    printf("1..%d\n", num_cases);
517
518    for (ii = 0; testcases[ii].description != NULL; ++ii) {
519        enum test_return ret = testcases[ii].function();
520        fflush(stdout);
521        alarm(60);
522        if (ret == TEST_SKIP) {
523            fprintf(stdout, "ok # SKIP %d - %s\n", ii + 1, testcases[ii].description);
524        } else if (ret == TEST_PASS) {
525            fprintf(stdout, "ok %d - %s\n", ii + 1, testcases[ii].description);
526        } else {
527            fprintf(stdout, "not ok %d - %s\n", ii + 1, testcases[ii].description);
528            exitcode = 1;
529        }
530        fflush(stdout);
531    }
532
533    return exitcode;
534}
535