#include <stic.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> /* errno */ #include <signal.h> /* SIGTERM */ #include <stdio.h> /* fclose() fdopen() fgets() fprintf() fputs() snprintf() */ #include <stdlib.h> /* exit() */ #include <string.h> /* strerror() */ #include <test-utils.h> #include "../../src/compat/fs_limits.h" #include "../../src/utils/gmux.h" #include "../../src/utils/shmem.h" #include "../../src/utils/utils.h" static void spawn_regcmd(int number); static void send_query(int instance, const char query[]); static void receive_ack(int instance); static void check_is_initial(int instance, const char reglist[]); static void receive_answer(int instance, char lnbuf[]); static void sync_to_from(int instance); static void sync_from(int instance); static void check_register_contents(int instance, char register_name, const char expected_content[]); static void test_pat(char result[], size_t patsz, char register_name, size_t pat_id_every, char p0, char p1); static void sync_disable(int instance); static pid_t popen2(const char cmd[], FILE **in, FILE **out); #define LINE_SIZE 32768 #define NUM_INSTANCES 3 #define TEST_REGISTERS "abcdefghijklmnopqrstuvwxyz" #define TEST_REGISTERS_MINUS_D "abcefghijklmnopqrstuvwxyz" #define TEST_REGISTERS_MINUS_DE "abcfghijklmnopqrstuvwxyz" #define TEST_REGISTERS_MINUS_DEF "abcghijklmnopqrstuvwxyz" #define TEST_REGISTERS_MINUS_DEFG "abchijklmnopqrstuvwxyz" #define TEST_EXPECT_FOR_D "d,3,nd1,nd2,newd," #define TEST_EXPECT_FOR_E "e,4,le1,le2,le3,longerthanbeforee," #define TEST_EXPECT_FOR_G "g,1,G," static FILE *instance_stdin[NUM_INSTANCES]; static FILE *instance_stdout[NUM_INSTANCES]; static char pat4kib[4096 + 8]; static char *tmpdir_value; /* All of the tests need to run in exactly the order given for correct * results. */ /* An update of Wine broke something, specifically second instance of helper * application doesn't seem to be started in SETUP_ONCE. */ /* Tests basic transfer of values from 0 to 1 and spawns processes. */ SETUP_ONCE() { if(!not_wine()) { return; } /* Put all the temporary files into sandbox to avoid issues with running tests * from multiple accounts. */ char sandbox[PATH_MAX + 1]; make_abs_path(sandbox, sizeof(sandbox), SANDBOX_PATH, "", NULL); tmpdir_value = mock_env("TMPDIR", sandbox); /* Make sure nothing is retained from previous tests. */ gmux_destroy(gmux_create("regs-test-shmem")); shmem_destroy(shmem_create("regs-test-shmem", 10, 10)); /* Spawn processes. */ spawn_regcmd(0); spawn_regcmd(1); /* Initial register values. */ const char *curletr; for(curletr = TEST_REGISTERS; *curletr != '\0'; ++curletr) { fprintf(instance_stdin[0], "set,%c,_initial%c,i%c1,i%c2,i%c3\n", *curletr, *curletr, *curletr, *curletr, *curletr); fprintf(instance_stdin[1], "set,%c,lossval%c,l%c1,l%c2,l%c3\n", *curletr, *curletr, *curletr, *curletr, *curletr); } /* Enable shared memory synchronization. */ send_query(0, "sync_enable,test-shmem\n"); receive_ack(0); send_query(1, "sync_enable,test-shmem\n"); receive_ack(1); } TEARDOWN_ONCE() { if(!not_wine()) { return; } /* Make sure nothing is retained from the tests. */ gmux_destroy(gmux_create("regs-test-shmem")); shmem_destroy(shmem_create("regs-test-shmem", 10, 10)); unmock_env("TMPDIR", tmpdir_value); } static void spawn_regcmd(int number) { const int debug = (getenv("DEBUG") != NULL); const char *cmd = debug ? "bin" SL "debug" SL "regs_shmem_app" : "bin" SL "regs_shmem_app"; if(popen2(cmd, &instance_stdin[number], &instance_stdout[number]) == (pid_t)-1) { perror("Failed to call fork"); exit(1); } setvbuf(instance_stdin[number], NULL, _IONBF, 0); } static void send_query(int instance, const char query[]) { if(fputs(query, instance_stdin[instance]) == EOF) { fprintf(stderr, "Failed to write query to FD %p " "(instance %d): %s; query was %s", instance_stdin[instance], instance, strerror(errno), query); exit(1); } } static void receive_ack(int instance) { char bakval; char lnbuf[LINE_SIZE]; receive_answer(instance, lnbuf); bakval = lnbuf[3]; /* Cut off ,... after ack */ lnbuf[3] = '\0'; if(strcmp(lnbuf, "ack") != 0) { lnbuf[3] = bakval; fprintf(stderr, "Error: Did not receive ACK but the following " "response from instance %d: %s\n", instance, lnbuf); exit(1); } } static void check_is_initial(int instance, const char reglist[]) { const char *curletr; for(curletr = reglist; *curletr != '\0'; ++curletr) { char cmp[27]; snprintf(cmp, sizeof(cmp), "%c,4,_initial%c,i%c1,i%c2,i%c3,", *curletr, *curletr, *curletr, *curletr, *curletr); check_register_contents(instance, *curletr, cmp); } } static void receive_answer(int instance, char lnbuf[]) { if(fgets(lnbuf, LINE_SIZE, instance_stdout[instance]) == NULL) { printf("Child (instance %d) unexpectedly sent eof\n", instance); exit(1); } /* Replace \n with '\0'. */ lnbuf[strlen(lnbuf) - 1] = '\0'; } TEST(make_sure_instance_0_does_not_lose_data, IF(not_wine)) { sync_from(0); check_is_initial(0, TEST_REGISTERS); } static void sync_to_from(int instance) { send_query(instance, "sync_to\n"); receive_ack(instance); sync_from(!instance); } static void sync_from(int instance) { send_query(instance, "sync_from\n"); receive_ack(instance); } TEST(make_sure_sync_to_instance_1_works, IF(not_wine)) { sync_from(1); check_is_initial(1, TEST_REGISTERS); } TEST(chg_update_in_place, IF(not_wine)) { send_query(1, "set,d,newd,nd1,nd2\n"); sync_to_from(1); check_register_contents(0, 'd', TEST_EXPECT_FOR_D); /* check others */ check_is_initial(0, TEST_REGISTERS_MINUS_D); } static void check_register_contents(int instance, char register_name, const char expected_content[]) { char lnbuf[LINE_SIZE]; char query[7]; strcpy(query, "get,X\n"); query[4] = register_name; send_query(instance, query); receive_answer(instance, lnbuf); assert_string_equal(expected_content, lnbuf); } TEST(chg_append_to_end, IF(not_wine)) { send_query(1, "set,e,longerthanbeforee,le1,le2,le3\n"); sync_to_from(1); /* Ignore D. */ /* Check E. */ check_register_contents(0, 'e', TEST_EXPECT_FOR_E); /* Check others. */ check_is_initial(0, TEST_REGISTERS_MINUS_DE); } TEST(chg_double_allocation_size_till_fit, IF(not_wine)) { test_pat(pat4kib, 4096, 'f', 128, '4', 'f'); /* Check initial. */ check_is_initial(0, TEST_REGISTERS_MINUS_DEF); } /* Result needs to have patsz + 8 entries (set,X, + \n + 0-byte). */ static void test_pat(char result[], size_t patsz, char register_name, size_t pat_id_every, char p0, char p1) { /* Prepare pattern. */ size_t i; char pre[] = "set,X,"; strcpy(result, pre); result[4] = register_name; for(i = 0; i < patsz; ++i) { result[sizeof(pre) - 1 + i] = ((i % pat_id_every) == 0)? p0: p1; } strcpy(result + patsz + 6, "\n"); /* add \n and 0 terminator */ send_query(1, result); sync_to_from(1); /* Change set,f, to [se]f,1, */ result[2] = register_name; char constpartcmp[] = ",1,"; memcpy(result + 3, constpartcmp, sizeof(constpartcmp) - 1); /* For comparison change \n to , */ result[patsz + 6] = ','; /* +2 to skip `se` */ check_register_contents(0, register_name, result + 2); } TEST(chg_double_allocation_to_max, IF(not_wine)) { char result[16384 + 8]; test_pat(result, 16384, 'g', 512, '6', 'g'); check_is_initial(0, TEST_REGISTERS_MINUS_DEFG); } TEST(chg_halve_allocation, IF(not_wine)) { send_query(0, "set,g,G\n"); sync_to_from(0); check_register_contents(1, 'g', TEST_EXPECT_FOR_G); check_is_initial(1, TEST_REGISTERS_MINUS_DEFG); } TEST(handover, IF(not_wine)) { /* Open third instance. */ spawn_regcmd(2); send_query(2, "sync_enable,test-shmem\n"); receive_ack(2); /* Close previous instances. */ sync_disable(0); sync_disable(1); sync_from(2); check_is_initial(2, TEST_REGISTERS_MINUS_DEFG); check_register_contents(2, 'd', TEST_EXPECT_FOR_D); check_register_contents(2, 'e', TEST_EXPECT_FOR_E); check_register_contents(2, 'f', pat4kib + 2); check_register_contents(2, 'g', TEST_EXPECT_FOR_G); } static void sync_disable(int instance) { send_query(instance, "sync_disable\n"); receive_ack(instance); fclose(instance_stdin[instance]); fclose(instance_stdout[instance]); } TEST(teardown_once, IF(not_wine)) { sync_disable(2); } #ifndef _WIN32 static pid_t popen2(const char cmd[], FILE **in, FILE **out) { pid_t pid; int in_pipe[2]; int out_pipe[2]; if(pipe(out_pipe) != 0) { return (pid_t)-1; } if(pipe(in_pipe) != 0) { close(out_pipe[0]); close(out_pipe[1]); return (pid_t)-1; } if((pid = fork()) == -1) { close(in_pipe[0]); close(in_pipe[1]); close(out_pipe[0]); close(out_pipe[1]); return (pid_t)-1; } if(pid == 0) { close(in_pipe[1]); close(out_pipe[0]); if(dup2(in_pipe[0], STDIN_FILENO) == -1) { _Exit(EXIT_FAILURE); } if(dup2(out_pipe[1], STDOUT_FILENO) == -1) { _Exit(EXIT_FAILURE); } if(dup2(out_pipe[1], STDERR_FILENO) == -1) { _Exit(EXIT_FAILURE); } close(in_pipe[0]); close(out_pipe[1]); execvp("/bin/sh", make_execv_array("/bin/sh", "-c", (char *)cmd)); _Exit(127); } close(in_pipe[0]); close(out_pipe[1]); *in = fdopen(in_pipe[1], "w"); if(*in == NULL) { close(in_pipe[1]); close(out_pipe[0]); kill(pid, SIGTERM); return (pid_t)-1; } *out = fdopen(out_pipe[0], "r"); if(*out == NULL) { fclose(*in); close(out_pipe[0]); kill(pid, SIGTERM); return (pid_t)-1; } return pid; } #else /* Runs command in background and redirects all its streams to file streams * which are set. Returns (pid_t)0 or (pid_t)-1 on error. */ static pid_t popen2_int(const char cmd[], FILE **in, FILE **out, int in_pipe[2], int out_pipe[2]) { const char *args[4]; int code; if(_dup2(in_pipe[0], _fileno(stdin)) != 0) return (pid_t)-1; if(_dup2(out_pipe[1], _fileno(stdout)) != 0) return (pid_t)-1; if(_dup2(out_pipe[1], _fileno(stderr)) != 0) return (pid_t)-1; args[0] = "cmd"; args[1] = "/C"; args[2] = cmd; args[3] = NULL; code = _spawnvp(P_NOWAIT, "cmd", args); if(code == 0) { return (pid_t)-1; } if((*in = _fdopen(in_pipe[1], "w")) == NULL) return (pid_t)-1; if((*out = _fdopen(out_pipe[0], "r")) == NULL) { fclose(*in); return (pid_t)-1; } return 0; } static pid_t popen2(const char cmd[], FILE **in, FILE **out) { int in_fd, in_pipe[2]; int out_fd, out_pipe[2]; int err_fd; pid_t pid; if(_pipe(in_pipe, 32*1024, O_NOINHERIT) != 0) { return (pid_t)-1; } if(_pipe(out_pipe, 32*1024, O_NOINHERIT) != 0) { close(in_pipe[0]); close(in_pipe[1]); return (pid_t)-1; } in_fd = dup(_fileno(stdin)); out_fd = dup(_fileno(stdout)); err_fd = dup(_fileno(stderr)); pid = popen2_int(cmd, in, out, in_pipe, out_pipe); _close(in_pipe[0]); _close(out_pipe[1]); _dup2(in_fd, _fileno(stdin)); _dup2(out_fd, _fileno(stdout)); _dup2(err_fd, _fileno(stderr)); _close(in_fd); _close(out_fd); _close(err_fd); if(pid == (pid_t)-1) { _close(in_pipe[1]); _close(out_pipe[0]); } return pid; } #endif /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 filetype=c : */