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