#include "ct/ct.h" #include "dat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int srvpid, size; // Global timeout set for reading response in tests; 5sec. static int64 timeout = 5000000000LL; // Allocation pattern for wrapfalloc that replaces falloc in tests. // Zero value at N-th element means that N-th call to the falloc // should fail with ENOSPC result. static byte fallocpat[3]; static int exist(char *path) { struct stat s; int r = stat(path, &s); return r != -1; } static int wrapfalloc(int fd, int size) { static size_t c = 0; printf("\nwrapfalloc: fd=%d size=%d\n", fd, size); if (c >= sizeof(fallocpat) || !fallocpat[c++]) { return ENOSPC; } return rawfalloc(fd, size); } static void muststart(char *a0, char *a1, char *a2, char *a3, char *a4) { srvpid = fork(); if (srvpid < 0) { twarn("fork"); exit(1); } if (srvpid > 0) { printf("%s %s %s %s %s\n", a0, a1, a2, a3, a4); printf("start server pid=%d\n", srvpid); usleep(100000); // .1s; time for the child to bind to its port return; } /* now in child */ execlp(a0, a0, a1, a2, a3, a4, NULL); } static int mustdiallocal(int port) { struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(port), }; int r = inet_aton("127.0.0.1", &addr.sin_addr); if (!r) { errno = EINVAL; twarn("inet_aton"); exit(1); } int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { twarn("socket"); exit(1); } // Fix of the benchmarking issue on Linux. See issue #430. int flags = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(int))) { twarn("setting TCP_NODELAY on fd %d", fd); exit(1); } r = connect(fd, (struct sockaddr *)&addr, sizeof addr); if (r == -1) { twarn("connect"); exit(1); } return fd; } static int mustdialunix(char *socket_file) { struct sockaddr_un addr; const size_t maxlen = sizeof(addr.sun_path); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, maxlen, "%s", socket_file); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { twarn("socket"); exit(1); } int r = connect(fd, (struct sockaddr *)&addr, sizeof addr); if (r == -1) { twarn("connect"); exit(1); } return fd; } static void exit_process(int signum) { UNUSED_PARAMETER(signum); exit(0); } static void set_sig_handler() { struct sigaction sa; sa.sa_flags = 0; int r = sigemptyset(&sa.sa_mask); if (r == -1) { twarn("sigemptyset()"); exit(111); } // This is required to trigger gcov on exit. See issue #443. sa.sa_handler = exit_process; r = sigaction(SIGTERM, &sa, 0); if (r == -1) { twarn("sigaction(SIGTERM)"); exit(111); } } // Kill the srvpid (child process) with SIGTERM to give it a chance // to write gcov data to the filesystem before ct kills it with SIGKILL. // Do nothing in case of srvpid==0; child was already killed. static void kill_srvpid(void) { if (!srvpid) return; kill(srvpid, SIGTERM); waitpid(srvpid, 0, 0); srvpid = 0; } #define SERVER() (progname=__func__, mustforksrv()) #define SERVER_UNIX() (progname=__func__, mustforksrv_unix()) // Forks the server storing the pid in srvpid. // The parent process returns port assigned. // The child process serves until the SIGTERM is received by it. static int mustforksrv(void) { struct sockaddr_in addr; srv.sock.fd = make_server_socket("127.0.0.1", "0"); if (srv.sock.fd == -1) { puts("mustforksrv failed"); exit(1); } socklen_t len = sizeof(addr); int r = getsockname(srv.sock.fd, (struct sockaddr *)&addr, &len); if (r == -1 || len > sizeof(addr)) { puts("mustforksrv failed"); exit(1); } int port = ntohs(addr.sin_port); srvpid = fork(); if (srvpid < 0) { twarn("fork"); exit(1); } if (srvpid > 0) { // On exit the parent (test) sends SIGTERM to the child. atexit(kill_srvpid); printf("start server port=%d pid=%d\n", port, srvpid); return port; } /* now in child */ set_sig_handler(); prot_init(); srv_acquire_wal(&srv); srvserve(&srv); /* does not return */ exit(1); /* satisfy the compiler */ } static char * mustforksrv_unix(void) { static char path[90]; char name[95]; snprintf(path, sizeof(path), "%s/socket", ctdir()); snprintf(name, sizeof(name), "unix:%s", path); srv.sock.fd = make_server_socket(name, NULL); if (srv.sock.fd == -1) { puts("mustforksrv_unix failed"); exit(1); } srvpid = fork(); if (srvpid < 0) { twarn("fork"); exit(1); } if (srvpid > 0) { // On exit the parent (test) sends SIGTERM to the child. atexit(kill_srvpid); printf("start server socket=%s\n", path); assert(exist(path)); return path; } /* now in child */ set_sig_handler(); prot_init(); srv_acquire_wal(&srv); srvserve(&srv); /* does not return */ exit(1); /* satisfy the compiler */ } static char * readline(int fd) { char c = 0, p = 0; static char buf[1024]; fd_set rfd; struct timeval tv; printf("<%d ", fd); fflush(stdout); size_t i = 0; for (;;) { FD_ZERO(&rfd); FD_SET(fd, &rfd); tv.tv_sec = timeout / 1000000000; tv.tv_usec = (timeout/1000) % 1000000; int r = select(fd+1, &rfd, NULL, NULL, &tv); switch (r) { case 1: break; case 0: fputs("timeout", stderr); exit(8); case -1: perror("select"); exit(1); default: fputs("unknown error", stderr); exit(3); } // TODO: try reading into a buffer to improve performance. // See related issue #430. r = read(fd, &c, 1); if (r == -1) { perror("write"); exit(1); } if (i >= sizeof(buf)-1) { fputs("response too big", stderr); exit(4); } putc(c, stdout); fflush(stdout); buf[i++] = c; if (p == '\r' && c == '\n') { break; } p = c; } buf[i] = '\0'; return buf; } static void ckresp(int fd, char *exp) { char *line = readline(fd); assertf(strcmp(exp, line) == 0, "\"%s\" != \"%s\"", exp, line); } static void ckrespsub(int fd, char *sub) { char *line = readline(fd); assertf(strstr(line, sub), "\"%s\" not in \"%s\"", sub, line); } static void writefull(int fd, char *s, int n) { int c; for (; n; n -= c) { c = write(fd, s, n); if (c == -1) { perror("write"); exit(1); } s += c; } } static void mustsend(int fd, char *s) { writefull(fd, s, strlen(s)); printf(">%d %s", fd, s); fflush(stdout); } static int filesize(char *path) { struct stat s; int r = stat(path, &s); if (r == -1) { twarn("stat"); exit(1); } return s.st_size; } void cttest_unknown_command() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "nont10knowncommand\r\n"); ckresp(fd, "UNKNOWN_COMMAND\r\n"); } void cttest_too_long_commandline() { int port = SERVER(); int fd = mustdiallocal(port); int i; for (i = 0; i < 10; i++) mustsend(fd, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // 50 bytes mustsend(fd, "\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); // Issue another command and check that reponse is not "UNKNOWN_COMMAND" // as described in issue #337 mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "A\r\n"); ckresp(fd, "INSERTED 1\r\n"); } void cttest_put_in_drain() { enter_drain_mode(SIGUSR1); int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "x\r\n"); ckresp(fd, "DRAINING\r\n"); } void cttest_peek_ok() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "a\r\n"); ckresp(fd, "INSERTED 1\r\n"); mustsend(fd, "peek 1\r\n"); ckresp(fd, "FOUND 1 1\r\n"); ckresp(fd, "a\r\n"); } void cttest_peek_not_found() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "a\r\n"); ckresp(fd, "INSERTED 1\r\n"); mustsend(fd, "peek 2\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "peek 18446744073709551615\r\n"); // UINT64_MAX ckresp(fd, "NOT_FOUND\r\n"); } void cttest_peek_ok_unix() { char *name = SERVER_UNIX(); int fd = mustdialunix(name); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "a\r\n"); ckresp(fd, "INSERTED 1\r\n"); mustsend(fd, "peek 1\r\n"); ckresp(fd, "FOUND 1 1\r\n"); ckresp(fd, "a\r\n"); unlink(name); } void cttest_unix_auto_removal() { // Twice, to trigger autoremoval SERVER_UNIX(); kill_srvpid(); SERVER_UNIX(); } void cttest_peek_bad_format() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "peek 18446744073709551616\r\n"); // UINT64_MAX+1 ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "peek 184467440737095516160000000000000000000000000000\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "peek foo111\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "peek 111foo\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); } void cttest_peek_delayed() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "peek-delayed\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "A\r\n"); ckresp(fd, "INSERTED 1\r\n"); mustsend(fd, "put 0 99 1 1\r\n"); mustsend(fd, "B\r\n"); ckresp(fd, "INSERTED 2\r\n"); mustsend(fd, "put 0 1 1 1\r\n"); mustsend(fd, "C\r\n"); ckresp(fd, "INSERTED 3\r\n"); mustsend(fd, "peek-delayed\r\n"); ckresp(fd, "FOUND 3 1\r\n"); ckresp(fd, "C\r\n"); mustsend(fd, "delete 3\r\n"); ckresp(fd, "DELETED\r\n"); mustsend(fd, "peek-delayed\r\n"); ckresp(fd, "FOUND 2 1\r\n"); ckresp(fd, "B\r\n"); mustsend(fd, "delete 2\r\n"); ckresp(fd, "DELETED\r\n"); mustsend(fd, "peek-delayed\r\n"); ckresp(fd, "NOT_FOUND\r\n"); } void cttest_peek_buried_kick() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "A\r\n"); ckresp(fd, "INSERTED 1\r\n"); // cannot bury unreserved job mustsend(fd, "bury 1 0\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "peek-buried\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "reserve-with-timeout 0\r\n"); ckresp(fd, "RESERVED 1 1\r\n"); ckresp(fd, "A\r\n"); // now we can bury mustsend(fd, "bury 1 0\r\n"); ckresp(fd, "BURIED\r\n"); mustsend(fd, "peek-buried\r\n"); ckresp(fd, "FOUND 1 1\r\n"); ckresp(fd, "A\r\n"); // kick and verify the job is ready mustsend(fd, "kick 1\r\n"); ckresp(fd, "KICKED 1\r\n"); mustsend(fd, "peek-buried\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "peek-ready\r\n"); ckresp(fd, "FOUND 1 1\r\n"); ckresp(fd, "A\r\n"); // nothing is left to kick mustsend(fd, "kick 1\r\n"); ckresp(fd, "KICKED 0\r\n"); } void cttest_touch_bad_format() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "touch a111\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "touch 111a\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "touch !@#!@#\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); } void cttest_touch_not_found() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "touch 1\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "touch 100000000000000\r\n"); ckresp(fd, "NOT_FOUND\r\n"); } void cttest_bury_bad_format() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "bury 111abc 2\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "bury 111\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "bury 111 222abc\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); } void cttest_kickjob_bad_format() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "kick-job a111\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "kick-job 111a\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); mustsend(fd, "kick-job !@#!@#\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); } void cttest_kickjob_buried() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "A\r\n"); ckresp(fd, "INSERTED 1\r\n"); mustsend(fd, "reserve\r\n"); ckresp(fd, "RESERVED 1 1\r\n"); ckresp(fd, "A\r\n"); mustsend(fd, "bury 1 0\r\n"); ckresp(fd, "BURIED\r\n"); mustsend(fd, "kick-job 100\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "kick-job 1\r\n"); ckresp(fd, "KICKED\r\n"); mustsend(fd, "kick-job 1\r\n"); ckresp(fd, "NOT_FOUND\r\n"); } void cttest_kickjob_delayed() { int port = SERVER(); int fd = mustdiallocal(port); // jid=1 - no delay, jid=2 - delay mustsend(fd, "put 0 0 1 1\r\n"); mustsend(fd, "A\r\n"); ckresp(fd, "INSERTED 1\r\n"); mustsend(fd, "put 0 10 1 1\r\n"); mustsend(fd, "B\r\n"); ckresp(fd, "INSERTED 2\r\n"); mustsend(fd, "kick-job 1\r\n"); ckresp(fd, "NOT_FOUND\r\n"); mustsend(fd, "kick-job 2\r\n"); ckresp(fd, "KICKED\r\n"); mustsend(fd, "kick-job 2\r\n"); ckresp(fd, "NOT_FOUND\r\n"); } void cttest_pause() { int64 s; int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 0 1\r\n"); mustsend(fd, "x\r\n"); ckresp(fd, "INSERTED 1\r\n"); s = nanoseconds(); mustsend(fd, "pause-tube default 1\r\n"); ckresp(fd, "PAUSED\r\n"); mustsend(fd, "reserve\r\n"); ckresp(fd, "RESERVED 1 1\r\n"); ckresp(fd, "x\r\n"); assert(nanoseconds() - s >= 1000000000); // 1s } void cttest_underscore() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "use x_y\r\n"); ckresp(fd, "USING x_y\r\n"); } void cttest_2cmdpacket() { int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "use a\r\nuse b\r\n"); ckresp(fd, "USING a\r\n"); ckresp(fd, "USING b\r\n"); } void cttest_too_big() { job_data_size_limit = 10; int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 0 11\r\n"); mustsend(fd, "delete 9999\r\n"); mustsend(fd, "put 0 0 0 1\r\n"); mustsend(fd, "x\r\n"); ckresp(fd, "JOB_TOO_BIG\r\n"); ckresp(fd, "INSERTED 1\r\n"); } void cttest_job_size_invalid() { job_data_size_limit = JOB_DATA_SIZE_LIMIT_MAX; int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 0 4294967296\r\n"); mustsend(fd, "put 0 0 0 10b\r\n"); mustsend(fd, "put 0 0 0 --!@#$%^&&**()0b\r\n"); mustsend(fd, "put 0 0 0 1\r\n"); mustsend(fd, "x\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); ckresp(fd, "BAD_FORMAT\r\n"); ckresp(fd, "INSERTED 1\r\n"); } void cttest_job_size_max_plus_1() { /* verify that server reject the job larger than maximum allowed. */ job_data_size_limit = JOB_DATA_SIZE_LIMIT_MAX; int port = SERVER(); int fd = mustdiallocal(port); mustsend(fd, "put 0 0 0 1073741825\r\n"); const int len = 1024*1024; char body[len+1]; memset(body, 'a', len); body[len] = 0; int i; for (i=0; i 0) { srv.wal.dir = ctdir(); srv.wal.use = 1; srv.wal.filesize = walsize; srv.wal.syncrate = syncrate_ms * 1000000; srv.wal.wantsync = sync; } job_data_size_limit = JOB_DATA_SIZE_LIMIT_MAX; int port = SERVER(); int fd = mustdiallocal(port); char buf[50], put[50]; char body[size+1]; memset(body, 'a', size); body[size] = 0; ctsetbytes(size); sprintf(put, "put 0 0 0 %d\r\n", size); ctresettimer(); int i; for (i = 0; i < n; i++) { mustsend(fd, put); mustsend(fd, body); mustsend(fd, "\r\n"); ckrespsub(fd, "INSERTED "); sprintf(buf, "delete %d\r\n", i + 1); mustsend(fd, buf); ckresp(fd, "DELETED\r\n"); } ctstoptimer(); } void ctbench_put_delete_0008(int n) { bench_put_delete_size(n, 8, 0, 0, 0); } void ctbench_put_delete_1024(int n) { bench_put_delete_size(n, 1024, 0, 0, 0); } void ctbench_put_delete_8192(int n) { bench_put_delete_size(n, 8192, 0, 0, 0); } void ctbench_put_delete_81920(int n) { bench_put_delete_size(n, 81920, 0, 0, 0); } void ctbench_put_delete_wal_1024_fsync_000ms(int n) { bench_put_delete_size(n, 1024, 512000, 1, 0); } void ctbench_put_delete_wal_1024_fsync_050ms(int n) { bench_put_delete_size(n, 1024, 512000, 1, 50); } void ctbench_put_delete_wal_1024_fsync_200ms(int n) { bench_put_delete_size(n, 1024, 512000, 1, 200); } void ctbench_put_delete_wal_1024_no_fsync(int n) { bench_put_delete_size(n, 1024, 512000, 0, 0); } void ctbench_put_delete_wal_8192_fsync_000ms(int n) { bench_put_delete_size(n, 8192, 512000, 1, 0); } void ctbench_put_delete_wal_8192_fsync_050ms(int n) { bench_put_delete_size(n, 8192, 512000, 1, 50); } void ctbench_put_delete_wal_8192_fsync_200ms(int n) { bench_put_delete_size(n, 8192, 512000, 1, 200); } void ctbench_put_delete_wal_8192_no_fsync(int n) { bench_put_delete_size(n, 8192, 512000, 0, 0); }