http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/mini_htraced.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/mini_htraced.c b/htrace-c/src/test/mini_htraced.c new file mode 100644 index 0000000..31b5484 --- /dev/null +++ b/htrace-c/src/test/mini_htraced.c @@ -0,0 +1,599 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/conf.h" +#include "core/htrace.h" +#include "test/mini_htraced.h" +#include "test/temp_dir.h" +#include "test/test_config.h" +#include "test/test.h" +#include "util/log.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <json/json_object.h> +#include <json/json_tokener.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +/** + * The maximum size of the notification data sent from the htraced daemon on + * startup. + */ +#define MAX_NDATA 65536 + +/** + * The separator to use in between paths. TODO: portability + */ +#define PATH_LIST_SEP ':' + +/** + * The maximum number of arguments when launching an external process. + */ +#define MAX_LAUNCH_ARGS 32 + +/** + * Retry an operation that may get EINTR. + * The operation must return a non-negative value on success. + */ +#define RETRY_ON_EINTR(ret, expr) do { \ + ret = expr; \ + if (ret >= 0) \ + break; \ +} while (errno == EINTR); + +#define MINI_HTRACED_LAUNCH_REDIRECT_FDS 0x1 + +static void mini_htraced_open_snsock(struct mini_htraced *ht, char *err, + size_t err_len); + +static void mini_htraced_write_conf_file(struct mini_htraced *ht, + char *err, size_t err_len); + +static int mini_htraced_write_conf_key(FILE *fp, const char *key, + const char *fmt, ...) + __attribute__((format(printf, 3, 4))); + +static void mini_htraced_launch_daemon(struct mini_htraced *ht, + char *err, size_t err_len); + +/** + * Launch an external process. + * + * @param ht The mini htraced object. + * @param path The binary to launch + * @param err (out param) the error, or empty string on success. + * @param err_len The length of the error buffer. + * @param flags The flags. + * MINI_HTRACED_LAUNCH_REDIRECT_FDS: redirect + * stderr, stdout, stdin to null. + * finished successfully. + * @param ... Additional arguments to pass to the process. + * NULL_terminated. The first argument will + * always be the path to the binary. + * + * @return The new process ID, on success. -1 on failure. + * The error string will always be set on failure. + */ +pid_t mini_htraced_launch(const struct mini_htraced *ht, const char *path, + char *err, size_t err_len, int flags, ...) + __attribute__((sentinel)); + + +static void mini_htraced_read_startup_notification(struct mini_htraced *ht, + char *err, size_t err_len); + +static void parse_startup_notification(struct mini_htraced *ht, + char *ndata, size_t ndata_len, + char *err, size_t err_len); + +void mini_htraced_build(const struct mini_htraced_params *params, + struct mini_htraced **hret, + char *err, size_t err_len) +{ + struct mini_htraced *ht = NULL; + int i, ret; + + err[0] = '\0'; + ht = calloc(1, sizeof(*ht)); + if (!ht) { + snprintf(err, err_len, "out of memory allocating mini_htraced object"); + goto done; + } + ht->snsock = -1; + ht->root_dir = create_tempdir(params->name, 0777, err, err_len); + if (err[0]) { + goto done; + } + ret = register_tempdir_for_cleanup(ht->root_dir); + if (ret) { + snprintf(err, err_len, "register_tempdir_for_cleanup(%s) " + "failed: %s", ht->root_dir, terror(ret)); + goto done; + } + for (i = 0; i < NUM_DATA_DIRS; i++) { + if (asprintf(ht->data_dir + i, "%s/dir%d", ht->root_dir, i) < 0) { + ht->data_dir[i] = NULL; + snprintf(err, err_len, "failed to create path to data dir %d", i); + goto done; + } + } + if (asprintf(&ht->htraced_log_path, "%s/htraced.log", ht->root_dir) < 0) { + ht->htraced_log_path = NULL; + snprintf(err, err_len, "failed to create path to htraced.log"); + goto done; + } + if (asprintf(&ht->htraced_conf_path, "%s/htraced-conf.xml", + ht->root_dir) < 0) { + ht->htraced_conf_path = NULL; + snprintf(err, err_len, "failed to create path to htraced-conf.xml"); + goto done; + } + mini_htraced_open_snsock(ht, err, err_len); + if (err[0]) { + goto done; + } + mini_htraced_write_conf_file(ht, err, err_len); + if (err[0]) { + goto done; + } + mini_htraced_launch_daemon(ht, err, err_len); + if (err[0]) { + goto done; + } + mini_htraced_read_startup_notification(ht, err, err_len); + if (err[0]) { + goto done; + } + if (asprintf(&ht->client_conf_defaults, "%s=%s", + HTRACED_ADDRESS_KEY, ht->htraced_http_addr) < 0) { + ht->client_conf_defaults = NULL; + snprintf(err, err_len, "failed to allocate client conf defaults."); + goto done; + } + *hret = ht; + err[0] = '\0'; + +done: + if (err[0]) { + mini_htraced_free(ht); + } +} + +static int do_waitpid(pid_t pid, char *err, size_t err_len) +{ + err[0] = '\0'; + while (1) { + int status, res = waitpid(pid, &status, 0); + if (res < 0) { + if (errno == EINTR) { + continue; + } + snprintf(err, err_len, "waitpid(%lld) error: %s", + (long long)pid, terror(res)); + return -1; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + return -1; // signal or other exit + } +} + +void mini_htraced_stop(struct mini_htraced *ht) +{ + char err[512]; + size_t err_len = sizeof(err); + + if (!ht->htraced_pid_valid) { + return; + } + kill(ht->htraced_pid, SIGTERM); + ht->htraced_pid_valid = 0; + do_waitpid(ht->htraced_pid, err, err_len); + if (err[0]) { + fprintf(stderr, "%s\n", err); + } +} + +void mini_htraced_free(struct mini_htraced *ht) +{ + int i; + + if (!ht) { + return; + } + mini_htraced_stop(ht); + if (ht->root_dir) { + unregister_tempdir_for_cleanup(ht->root_dir); + if (!getenv("SKIP_CLEANUP")) { + recursive_unlink(ht->root_dir); + } + } + free(ht->root_dir); + for (i = 0; i < NUM_DATA_DIRS; i++) { + free(ht->data_dir[i]); + } + free(ht->htraced_log_path); + free(ht->htraced_conf_path); + free(ht->client_conf_defaults); + if (ht->snsock >= 0) { + close(ht->snsock); + ht->snsock = -1; + } + free(ht->htraced_http_addr); + free(ht); +} + +static void mini_htraced_open_snsock(struct mini_htraced *ht, char *err, + size_t err_len) +{ + struct sockaddr_in snaddr; + socklen_t len = sizeof(snaddr); + struct timeval tv; + + err[0] = '\0'; + memset(&snaddr, 0, sizeof(snaddr)); + snaddr.sin_family = AF_INET; + snaddr.sin_addr.s_addr = htonl(INADDR_ANY); + ht->snsock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (ht->snsock < 0) { + int res = errno; + snprintf(err, err_len, "Failed to create new socket: %s\n", + terror(res)); + return; + } + if (bind(ht->snsock, (struct sockaddr *) &snaddr, sizeof(snaddr)) < 0) { + int res = errno; + snprintf(err, err_len, "bind failed: %s\n", terror(res)); + return; + } + if (getsockname(ht->snsock, (struct sockaddr *)&snaddr, &len) < 0) { + int res = errno; + snprintf(err, err_len, "getsockname failed: %s\n", terror(res)); + return; + } + ht->snport = ntohs(snaddr.sin_port); + if (listen(ht->snsock, 32) < 0) { + int res = errno; + snprintf(err, err_len, "listen failed: %s\n", terror(res)); + return; + } + // On Linux, at least, this makes accept() time out after 30 seconds. I'm + // too lazy to use select() here. + tv.tv_sec = 30; + tv.tv_usec = 0; + setsockopt(ht->snsock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +} + +static void mini_htraced_write_conf_file(struct mini_htraced *ht, + char *err, size_t err_len) +{ + FILE *fp; + int res; + + err[0] = '\0'; + fp = fopen(ht->htraced_conf_path, "w"); + if (!fp) { + res = errno; + snprintf(err, err_len, "fopen(%s) failed: %s", + ht->htraced_conf_path, terror(res)); + goto error; + } + if (fprintf(fp, "\ +<?xml version=\"1.0\"?>\n\ +<?xml-stylesheet type=\"text/xsl\" href=\"configuration.xsl\"?>\n\ +<configuration>\n") < 0) { + goto ioerror; + } + if (mini_htraced_write_conf_key(fp, "log.path", "%s", + ht->htraced_log_path)) { + goto ioerror; + } + if (mini_htraced_write_conf_key(fp, "web.address", "127.0.0.1:0")) { + goto ioerror; + } + if (mini_htraced_write_conf_key(fp, "data.store.directories", + "%s%c%s", ht->data_dir[0], PATH_LIST_SEP, ht->data_dir[1])) { + goto ioerror; + } + if (mini_htraced_write_conf_key(fp, "startup.notification.address", + "localhost:%d", ht->snport)) { + goto ioerror; + } + if (mini_htraced_write_conf_key(fp, "log.level", "%s", "TRACE")) { + goto ioerror; + } + if (fprintf(fp, "</configuration>\n") < 0) { + goto ioerror; + } + res = fclose(fp); + if (res) { + snprintf(err, err_len, "fclose(%s) failed: %s", + ht->htraced_conf_path, terror(res)); + } + return; + +ioerror: + snprintf(err, err_len, "fprintf(%s) error", + ht->htraced_conf_path); +error: + if (fp) { + fclose(fp); + } +} + +static int mini_htraced_write_conf_key(FILE *fp, const char *key, + const char *fmt, ...) +{ + va_list ap; + + if (fprintf(fp, " <property>\n <name>%s</name>\n <value>", + key) < 0) { + return 1; + } + va_start(ap, fmt); + if (vfprintf(fp, fmt, ap) < 0) { + va_end(ap); + return 1; + } + va_end(ap); + if (fprintf(fp, "</value>\n </property>\n") < 0) { + return 1; + } + return 0; +} + +pid_t mini_htraced_launch(const struct mini_htraced *ht, const char *path, + char *err, size_t err_len, int flags, ...) +{ + pid_t pid; + int res, num_args = 0; + va_list ap; + const char *args[MAX_LAUNCH_ARGS + 1]; + const char * const env[] = { + "HTRACED_CONF_DIR=.", "HTRACED_WEB_DIR=", NULL + }; + + err[0] = '\0'; + if (access(path, X_OK) < 0) { + snprintf(err, err_len, "The %s binary is not accessible and " + "executable.", path); + return -1; + } + va_start(ap, flags); + args[num_args++] = path; + while (1) { + const char *arg = va_arg(ap, char*); + if (!arg) { + break; + } + if (num_args >= MAX_LAUNCH_ARGS) { + va_end(ap); + snprintf(err, err_len, "Too many arguments to launch! The " + "maximum number of arguments is %d.\n", MAX_LAUNCH_ARGS); + return -1; + } + args[num_args++] = arg; + } + va_end(ap); + args[num_args++] = NULL; + + pid = fork(); + if (pid == -1) { + // Fork failed. + res = errno; + snprintf(err, err_len, "fork() failed for %s: %s\n", + path, terror(res)); + return -1; + } else if (pid == 0) { + // Child process. + // We don't want to delete the temporary directory when this child + // process exists. The parent process is responsible for that, if it + // is to be done at all. + unregister_tempdir_for_cleanup(ht->root_dir); + + // Make things nicer by exiting when the parent process exits. + prctl(PR_SET_PDEATHSIG, SIGHUP); + + if (flags & MINI_HTRACED_LAUNCH_REDIRECT_FDS) { + int null_fd; + RETRY_ON_EINTR(null_fd, open("/dev/null", O_WRONLY)); + if (null_fd < 0) { + _exit(127); + } + RETRY_ON_EINTR(res, dup2(null_fd, STDOUT_FILENO)); + if (res < 0) { + _exit(127); + } + RETRY_ON_EINTR(res, dup2(null_fd, STDERR_FILENO)); + if (res < 0) { + _exit(127); + } + } + if (chdir(ht->root_dir) < 0) { + _exit(127); + } + execve(path, (char *const*)args, (char * const*)env); + _exit(127); + } + // Parent process. + return pid; +} + +static void mini_htraced_launch_daemon(struct mini_htraced *ht, + char *err, size_t err_len) +{ + int flags = 0; + pid_t pid; + + if (!getenv("SKIP_CLEANUP")) { + flags |= MINI_HTRACED_LAUNCH_REDIRECT_FDS; + } + pid = mini_htraced_launch(ht, HTRACED_ABSPATH, err, err_len, flags, NULL); + if (err[0]) { + return; + } + ht->htraced_pid_valid = 1; + ht->htraced_pid = pid; +} + +void mini_htraced_dump_spans(struct mini_htraced *ht, + char *err, size_t err_len, + const char *path) +{ + pid_t pid; + int ret; + char *addr = NULL; + + err[0] = '\0'; + if (asprintf(&addr, "--addr=%s", ht->htraced_http_addr) < 0) { + addr = NULL; + snprintf(err, err_len, "OOM while allocating the addr string"); + return; + } + pid = mini_htraced_launch(ht, HTRACE_ABSPATH, err, err_len, 0, + addr, "dumpAll", path, NULL); + free(addr); + if (err[0]) { + return; + } + ret = do_waitpid(pid, err, err_len); + if (err[0]) { + return; + } + if (ret != EXIT_SUCCESS) { + snprintf(err, err_len, "%s returned non-zero exit status %d\n", + HTRACE_ABSPATH, ret); + return; + } +} + +static void mini_htraced_read_startup_notification(struct mini_htraced *ht, + char *err, size_t err_len) +{ + char *ndata = NULL; + int res, sock = -1; + size_t ndata_len = 0; + + err[0] = '\0'; + ndata = malloc(MAX_NDATA); + if (!ndata) { + snprintf(err, err_len, "failed to allocate %d byte buffer for " + "notification data.", MAX_NDATA); + goto done; + } + RETRY_ON_EINTR(sock, accept(ht->snsock, NULL, NULL)); + if (sock < 0) { + int e = errno; + snprintf(err, err_len, "accept failed: %s", terror(e)); + goto done; + } + while (ndata_len < MAX_NDATA) { + res = recv(sock, ndata + ndata_len, MAX_NDATA - ndata_len, 0); + if (res == 0) { + break; + } + if (res < 0) { + int e = errno; + if (e == EINTR) { + continue; + } + snprintf(err, err_len, "recv error: %s", terror(e)); + goto done; + } + ndata_len += res; + } + parse_startup_notification(ht, ndata, ndata_len, err, err_len); + if (err[0]) { + goto done; + } + +done: + if (sock >= 0) { + close(sock); + } + free(ndata); +} + +static void parse_startup_notification(struct mini_htraced *ht, + char *ndata, size_t ndata_len, + char *err, size_t err_len) +{ + struct json_tokener *tok = NULL; + struct json_object *root = NULL, *http_addr, *process_id; + int32_t pid; + + err[0] = '\0'; + tok = json_tokener_new(); + if (!tok) { + snprintf(err, err_len, "json_tokener_new failed."); + goto done; + } + root = json_tokener_parse_ex(tok, ndata, ndata_len); + if (!root) { + enum json_tokener_error jerr = json_tokener_get_error(tok); + snprintf(err, err_len, "Failed to parse startup notification: %s.", + json_tokener_error_desc(jerr)); + goto done; + } + // Find the http address, in the form of hostname:port, which the htraced + // is listening on. + if (!json_object_object_get_ex(root, "HttpAddr", &http_addr)) { + snprintf(err, err_len, "Failed to find HttpAddr in the startup " + "notification."); + goto done; + } + ht->htraced_http_addr = strdup(json_object_get_string(http_addr)); + if (!ht->htraced_http_addr) { + snprintf(err, err_len, "OOM"); + goto done; + } + // Check that the process ID from the startup notification matches the + // process ID from the fork. + if (!json_object_object_get_ex(root, "ProcessId", &process_id)) { + snprintf(err, err_len, "Failed to find ProcessId in the startup " + "notification."); + goto done; + } + pid = json_object_get_int(process_id); + if (pid != ht->htraced_pid) { + snprintf(err, err_len, "Startup notification pid was %lld, but the " + "htraced process id was %lld.", + (long long)pid, (long long)ht->htraced_pid); + goto done; + } + +done: + json_tokener_free(tok); + if (root) { + json_object_put(root); + } +} + +// vim: ts=4:sw=4:tw=79:et
http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/mini_htraced.h ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/mini_htraced.h b/htrace-c/src/test/mini_htraced.h new file mode 100644 index 0000000..a803f55 --- /dev/null +++ b/htrace-c/src/test/mini_htraced.h @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APACHE_HTRACE_TEST_MINI_HTRACED_H +#define APACHE_HTRACE_TEST_MINI_HTRACED_H + +/** + * @file mini_htraced.h + * + * Implements a mini htraced cluster which can be used in unit tests. + * + * This is useful for testing the htraced trace sink of the native client. + * + * This is an internal header, not intended for external use. + */ + +#include <unistd.h> /* for pid_t and size_t */ + +struct htrace_conf; + +#define NUM_DATA_DIRS 2 + +struct mini_htraced_params { + /** + * The name of the mini htraced process to start. + * This shows up in the test directory name and some other places. + * The memory should be managed by the caller. + */ + const char *name; + + /** + * The configuration to use, in string form. + * The memory should be managed by the caller. + */ + const char *confstr; +}; + +struct mini_htraced { + /** + * The process ID of the mini htraced. + */ + int pid; + + /** + * The path to the root directory that all the state associated with this + * mini_htraced will be placed under. Malloced. + */ + char *root_dir; + + /** + * Paths to the data directories to use for htraced. Malloced. + */ + char *data_dir[NUM_DATA_DIRS]; + + /** + * Path to the server's log file. Malloced. + */ + char *htraced_log_path; + + /** + * Path to the server's conf file. Malloced. + */ + char *htraced_conf_path; + + /** + * The client configuration defaults. Malloced. + */ + char *client_conf_defaults; + + /** + * The startup notification port. + */ + int snport; + + /** + * The startup notification socket, or -1 if the socket has been closed. + */ + int snsock; + + /** + * Nonzero if the htraced pid is valid. + */ + int htraced_pid_valid; + + /** + * The process ID of the htraced pid. + */ + pid_t htraced_pid; + + /** + * The HTTP address of the htraced, in hostname:port format. + */ + char *htraced_http_addr; +}; + +/** + * Build the mini HTraced cluster. + * + * @param params The parameters to use. + * @param ht (out param) The mini htraced object on success. + * @param err (out param) The error message if there was an + * error. + * @param err_len The length of the error buffer provided by the + * caller. + */ +void mini_htraced_build(const struct mini_htraced_params *params, struct mini_htraced **ht, + char *err, size_t err_len); + +/** + * Stop the htraced process. + * + * @param ht The mini htraced object. + */ +void mini_htraced_stop(struct mini_htraced *ht); + +/** + * Free the memory associated with the mini htraced object. + * + * @param ht The mini htraced object. + */ +void mini_htraced_free(struct mini_htraced *ht); + +/** + * Dump the spans contained in this htraced instance to a file. + * + * @param ht The mini htraced object. + * @param err (out param) The error message if there was an + * error. + * @param err_len The length of the error buffer provided by the + * caller. + * @param path The path to dump the spans to, in json form. + */ +void mini_htraced_dump_spans(struct mini_htraced *ht, + char *err, size_t err_len, + const char *path); + +#endif + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/process_id-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/process_id-unit.c b/htrace-c/src/test/process_id-unit.c new file mode 100644 index 0000000..5454eb0 --- /dev/null +++ b/htrace-c/src/test/process_id-unit.c @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/conf.h" +#include "test/test.h" +#include "util/log.h" +#include "util/process_id.h" + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +static struct htrace_conf *g_cnf; + +static struct htrace_log *g_lg; + +static int test_calculate_process_id(const char *expected, const char *fmt) +{ + char *process_id; + + process_id = calculate_process_id(g_lg, fmt, "fooproc"); + EXPECT_NONNULL(process_id); + EXPECT_STR_EQ(expected, process_id); + free(process_id); + + return EXIT_SUCCESS; +} + +static int test_calculate_process_ids(void) +{ + char buf[128]; + + EXPECT_INT_ZERO(test_calculate_process_id("my.name", "my.name")); + EXPECT_INT_ZERO(test_calculate_process_id("my.fooproc", "my.%{tname}")); + EXPECT_INT_ZERO(test_calculate_process_id("\%foo", "\%foo")); + EXPECT_INT_ZERO(test_calculate_process_id("fooproc", "%{tname}")); + EXPECT_INT_ZERO(test_calculate_process_id("\\fooproc", "\\\\%{tname}")); + EXPECT_INT_ZERO(test_calculate_process_id("\%{tname}", "\\%{tname}")); + + snprintf(buf, sizeof(buf), "me.%lld", (long long)getpid()); + EXPECT_INT_ZERO(test_calculate_process_id(buf, "me.%{pid}")); + + get_best_ip(g_lg, buf, sizeof(buf)); + EXPECT_INT_ZERO(test_calculate_process_id(buf, "%{ip}")); + + return EXIT_SUCCESS; +} + +int main(void) +{ + g_cnf = htrace_conf_from_strs("", ""); + EXPECT_NONNULL(g_cnf); + g_lg = htrace_log_alloc(g_cnf); + EXPECT_NONNULL(g_lg); + EXPECT_INT_ZERO(test_calculate_process_ids()); + htrace_log_free(g_lg); + htrace_conf_free(g_cnf); + + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rand-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/rand-unit.c b/htrace-c/src/test/rand-unit.c new file mode 100644 index 0000000..c02dbf1 --- /dev/null +++ b/htrace-c/src/test/rand-unit.c @@ -0,0 +1,93 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/conf.h" +#include "test/test.h" +#include "util/log.h" +#include "util/rand.h" + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct htrace_log *g_rand_unit_lg; + +#define ARRAY_SIZE 128 + +static int compare_u32(const void *v1, const void *v2) +{ + uint32_t a = *(uint32_t *)v1; + uint32_t b = *(uint32_t *)v2; + + if (a < b) { + return -1; + } else if (b < a) { + return 1; + } else { + return 0; + } +} + +/** + * Test that we can generate an array of unique uint32_t objects. + */ +static int test_u32_uniqueness(void) +{ + struct random_src *rnd = random_src_alloc(g_rand_unit_lg); + uint32_t prev, *arr; + int i, duplicate; + + do { + arr = calloc(ARRAY_SIZE, sizeof(uint32_t)); + EXPECT_NONNULL(arr); + for (i = 0; i < ARRAY_SIZE; i++) { + arr[i] = random_u32(rnd); + } + qsort(arr, ARRAY_SIZE, sizeof(arr[0]), compare_u32); + prev = 0; + duplicate = 0; + for (i = 0; i < ARRAY_SIZE; i++) { + if (arr[i] == prev) { + duplicate = 1; + } + prev = arr[i]; + } + } while (duplicate); + random_src_free(rnd); + free(arr); + return EXIT_SUCCESS; +} + +int main(void) +{ + struct htrace_conf *conf; + + conf = htrace_conf_from_strs("", ""); + EXPECT_NONNULL(conf); + g_rand_unit_lg = htrace_log_alloc(conf); + EXPECT_NONNULL(g_rand_unit_lg); + EXPECT_INT_ZERO(test_u32_uniqueness()); + htrace_log_free(g_rand_unit_lg); + htrace_conf_free(conf); + + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rtest.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/rtest.c b/htrace-c/src/test/rtest.c new file mode 100644 index 0000000..0ec5272 --- /dev/null +++ b/htrace-c/src/test/rtest.c @@ -0,0 +1,155 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/conf.h" +#include "core/htrace.h" +#include "core/span.h" +#include "test/rtest.h" +#include "test/span_table.h" +#include "test/span_util.h" +#include "test/test.h" +#include "util/log.h" + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define RECEIVER_TEST_TNAME "receiver-unit" + +/** + * @file rtestpp.cc + * + * The C receiver tests. + */ + +struct rtest_data { + struct htrace_conf *cnf; + struct htrace_sampler *always; + struct htracer *tracer; +}; + +static void get_receiver_test_prid(char *prid, size_t prid_len) +{ + snprintf(prid, prid_len, RECEIVER_TEST_TNAME "/%lld", (long long)getpid()); +} + +static int rtest_data_init(const char *conf_str, struct rtest_data **out) +{ + struct rtest_data *rdata = calloc(1, sizeof(*(rdata))); + EXPECT_NONNULL(rdata); + rdata->cnf = htrace_conf_from_strs(conf_str, + HTRACE_PROCESS_ID"=%{tname}/%{pid};sampler=always"); + EXPECT_NONNULL(rdata->cnf); + rdata->tracer = htracer_create(RECEIVER_TEST_TNAME, rdata->cnf); + EXPECT_NONNULL(rdata->tracer); + rdata->always = htrace_sampler_create(rdata->tracer, rdata->cnf); + EXPECT_NONNULL(rdata->always); + *out = rdata; + return EXIT_SUCCESS; +} + +static void rtest_data_free(struct rtest_data *rdata) +{ + htrace_sampler_free(rdata->always); + rdata->always = NULL; + htracer_free(rdata->tracer); + rdata->tracer = NULL; + htrace_conf_free(rdata->cnf); + rdata->cnf = NULL; + free(rdata); +} + +static int rtest_verify_table_size(struct rtest *rt, struct span_table *st) +{ + EXPECT_INT_EQ(rt->spans_created, span_table_size(st)); + + return EXIT_SUCCESS; +} + +static int doit(struct rtest_data *rdata) +{ + struct htrace_scope *scope1, *scope2, *scope2_5; + + scope1 = htrace_start_span(rdata->tracer, NULL, "part1"); + EXPECT_UINT64_GT(0L, htrace_scope_get_span_id(scope1)); + htrace_scope_close(scope1); + scope2 = htrace_start_span(rdata->tracer, NULL, "part2"); + EXPECT_UINT64_GT(0L, htrace_scope_get_span_id(scope2)); + scope2_5 = htrace_start_span(rdata->tracer, NULL, "part2.5"); + htrace_scope_close(scope2_5); + htrace_scope_close(scope2); + return EXIT_SUCCESS; +} + +int rtest_simple_run(struct rtest *rt, const char *conf_str) +{ + struct htrace_scope *scope0; + struct rtest_data *rdata = NULL; + + EXPECT_INT_ZERO(rtest_data_init(conf_str, &rdata)); + EXPECT_NONNULL(rdata); + scope0 = htrace_start_span(rdata->tracer, rdata->always, "doit"); + doit(rdata); + htrace_scope_close(scope0); + rt->spans_created = 4; + rtest_data_free(rdata); + return EXIT_SUCCESS; +} + +int rtest_simple_verify(struct rtest *rt, struct span_table *st) +{ + struct htrace_span *span; + uint64_t doit_id, part2_id; + char prid[128]; + + EXPECT_INT_ZERO(rtest_verify_table_size(rt, st)); + get_receiver_test_prid(prid, sizeof(prid)); + EXPECT_INT_ZERO(span_table_get(st, &span, "doit", prid)); + doit_id = span->span_id; + EXPECT_INT_ZERO(span->num_parents); + + EXPECT_INT_ZERO(span_table_get(st, &span, "part1", prid)); + EXPECT_INT_EQ(1, span->num_parents); + EXPECT_UINT64_EQ(doit_id, span->parent.single); + + EXPECT_INT_ZERO(span_table_get(st, &span, "part2", prid)); + EXPECT_INT_EQ(1, span->num_parents); + part2_id = span->span_id; + EXPECT_UINT64_EQ(doit_id, span->parent.single); + + EXPECT_INT_ZERO(span_table_get(st, &span, "part2.5", prid)); + EXPECT_INT_EQ(1, span->num_parents); + EXPECT_UINT64_EQ(part2_id, span->parent.single); + + return EXIT_SUCCESS; +} + +static struct rtest g_rtest_simple = { + "rtest_simple", + rtest_simple_run, + rtest_simple_verify, +}; + +struct rtest * const g_rtests[] = { + &g_rtest_simple, + NULL +}; + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rtest.h ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/rtest.h b/htrace-c/src/test/rtest.h new file mode 100644 index 0000000..52d0309 --- /dev/null +++ b/htrace-c/src/test/rtest.h @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APACHE_HTRACE_TEST_RTEST_H +#define APACHE_HTRACE_TEST_RTEST_H + +/** + * @file rtest.h + * + * Declares receiver tests. + * + * This is an internal header, not intended for external use. + */ + +struct span_table; + +/** + * A receiver test. + */ +struct rtest { + /** + * The name of the receiver test. + */ + const char *name; + + /** + * Run the receiver test. + * + * @param rt The receiver test. + * @param conf_str The configuration string to use. + * + * @return zero on success + */ + int (*run)(struct rtest *rt, const char *conf_str); + + /** + * Verify that the receiver test succeeded. + * + * @param rt The receiver test. + * @param st The span table. + * + * @return zero on success + */ + int (*verify)(struct rtest *rt, struct span_table *st); + + /** + * The number of spans that have been created by this test. + */ + int spans_created; +}; + +/** + * A NULL-terminated list of pointers to rtests. + */ +extern struct rtest * const g_rtests[]; + +#define RECEIVER_TEST_TNAME "receiver-unit" + +#endif + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/rtestpp.cc ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/rtestpp.cc b/htrace-c/src/test/rtestpp.cc new file mode 100644 index 0000000..649826c --- /dev/null +++ b/htrace-c/src/test/rtestpp.cc @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define __STDC_FORMAT_MACROS + +#include "core/htrace.hpp" + +extern "C" { +#include "core/conf.h" +#include "core/span.h" +#include "test/rtest.h" +#include "test/span_table.h" +#include "test/span_util.h" +#include "test/test.h" +#include "util/log.h" + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +} + +/** + * @file rtestpp.cc + * + * The C++ receiver tests. + */ + +using std::string; + +class RTestData { +public: + RTestData(struct rtest *rt, const char *conf_str) + : rt_(rt), + cnf_(string(HTRACE_PROCESS_ID"=%{tname}/%{pid};sampler=always;") + + string(conf_str).c_str()), + tracer_(RECEIVER_TEST_TNAME, cnf_), + always_(&tracer_, cnf_) + { + } + + // Test that initialization succeeeded + int TestInit() { + EXPECT_STR_EQ(RECEIVER_TEST_TNAME, tracer_.Name().c_str()); + EXPECT_STR_EQ("AlwaysSampler", always_.ToString().c_str()); + return EXIT_SUCCESS; + } + + struct rtest *rt_; + htrace::Conf cnf_; + htrace::Tracer tracer_; + htrace::Sampler always_; + +private: + RTestData(const RTestData &other); // disallow copying + RTestData& operator=(const RTestData &other); // disallow assignment +}; + +static void get_receiver_test_prid(char *prid, size_t prid_len) +{ + snprintf(prid, prid_len, RECEIVER_TEST_TNAME "/%lld", (long long)getpid()); +} + +static int rtest_generic_verify(struct rtest *rt, struct span_table *st) +{ + EXPECT_INT_EQ(rt->spans_created, span_table_size(st)); + + return EXIT_SUCCESS; +} + +static int doit(RTestData &tdata, struct rtest *rt) +{ + { + htrace::Scope scope1(tdata.tracer_, "part1"); + EXPECT_UINT64_GT(0L, scope1.GetSpanId()); + } + { + htrace::Scope scope2(tdata.tracer_, "part2"); + EXPECT_UINT64_GT(0L, scope2.GetSpanId()); + { + htrace::Scope scope2_5(tdata.tracer_, "part2.5"); + EXPECT_UINT64_GT(0L, scope2_5.GetSpanId()); + } + } + return EXIT_SUCCESS; +} + +int rtestpp_simple_run(struct rtest *rt, const char *conf_str) +{ + RTestData tdata(rt, conf_str); + EXPECT_INT_ZERO(tdata.TestInit()); + + htrace::Scope scope0(tdata.tracer_, tdata.always_, "doit"); + doit(tdata, rt); + rt->spans_created = 4; + return EXIT_SUCCESS; +} + +int rtestpp_simple_verify(struct rtest *rt, struct span_table *st) +{ + struct htrace_span *span; + uint64_t doit_id, part2_id; + char prid[128]; + + EXPECT_INT_ZERO(rtest_generic_verify(rt, st)); + get_receiver_test_prid(prid, sizeof(prid)); + EXPECT_INT_ZERO(span_table_get(st, &span, "doit", prid)); + doit_id = span->span_id; + EXPECT_INT_ZERO(span->num_parents); + + EXPECT_INT_ZERO(span_table_get(st, &span, "part1", prid)); + EXPECT_INT_EQ(1, span->num_parents); + EXPECT_UINT64_EQ(doit_id, span->parent.single); + + EXPECT_INT_ZERO(span_table_get(st, &span, "part2", prid)); + EXPECT_INT_EQ(1, span->num_parents); + part2_id = span->span_id; + EXPECT_UINT64_EQ(doit_id, span->parent.single); + + EXPECT_INT_ZERO(span_table_get(st, &span, "part2.5", prid)); + EXPECT_INT_EQ(1, span->num_parents); + EXPECT_UINT64_EQ(part2_id, span->parent.single); + + return EXIT_SUCCESS; +} + +struct rtest g_rtestpp_simple = { + "rtestpp_simple", + rtestpp_simple_run, + rtestpp_simple_verify, +}; + +struct rtest * const g_rtests[] = { + &g_rtestpp_simple, + NULL +}; + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/sampler-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/sampler-unit.c b/htrace-c/src/test/sampler-unit.c new file mode 100644 index 0000000..2a42292 --- /dev/null +++ b/htrace-c/src/test/sampler-unit.c @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/conf.h" +#include "core/htrace.h" +#include "sampler/sampler.h" +#include "test/test.h" +#include "util/log.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct htrace_conf *g_test_conf; + +static struct htrace_log *g_test_lg; + +static struct htracer *g_test_tracer; + +#define NUM_TEST_SAMPLES 1000 + +static int test_simple_sampler(const char *name, int expected) +{ + struct htrace_conf *conf; + struct htrace_sampler *smp; + char confstr[256] = { 0 }; + int i; + + if (name) { + snprintf(confstr, sizeof(confstr), "%s=%s", + HTRACE_SAMPLER_KEY, name); + } + conf = htrace_conf_from_strs(confstr, ""); + EXPECT_NONNULL(conf); + smp = htrace_sampler_create(g_test_tracer, conf); + EXPECT_NONNULL(smp); + for (i = 0; i < NUM_TEST_SAMPLES; i++) { + int s = smp->ty->next(smp); + EXPECT_INT_EQ(expected, s); + } + htrace_conf_free(conf); + htrace_sampler_free(smp); + return EXIT_SUCCESS; +} + +static int test_unconfigured_sampler() +{ + return test_simple_sampler(NULL, 0); +} + +static int test_never_sampler() +{ + return test_simple_sampler("never", 0); +} + +static int test_always_sampler() +{ + return test_simple_sampler("always", 1); +} + +#define NUM_PROB_TEST_SAMPLES 500000 + +static int test_prob_sampler(double target, double slop) +{ + struct htrace_conf *conf; + struct htrace_sampler *smp; + char confstr[256] = { 0 }; + double actual, diff; + + snprintf(confstr, sizeof(confstr), + "sampler=prob;prob.sampler.fraction=%g", target); + conf = htrace_conf_from_strs(confstr, ""); + EXPECT_NONNULL(conf); + smp = htrace_sampler_create(g_test_tracer, conf); + EXPECT_NONNULL(smp); + do { + int i; + uint64_t total = 0; + + for (i = 0; i < NUM_PROB_TEST_SAMPLES; i++) { + int val = smp->ty->next(smp); + if ((val != 0) && (val != 1)) { + htrace_log(g_test_lg, "Invalid return from sampler: " + "expected 0 or 1, but got %d\n", val); + abort(); + } + total += val; + } + actual = ((double)total) / (double)NUM_PROB_TEST_SAMPLES; + diff = fabs(target - actual); + htrace_log(g_test_lg, "After %d samples, fraction is %g. Target " + "was %g. %s\n", NUM_PROB_TEST_SAMPLES, actual, target, + (diff < slop) ? "Done. " : "Retrying."); + } while (diff >= slop); + htrace_conf_free(conf); + htrace_sampler_free(smp); + return EXIT_SUCCESS; +} + +int main(void) +{ + g_test_conf = htrace_conf_from_strs("", HTRACE_PROCESS_ID"=sampler-unit"); + EXPECT_NONNULL(g_test_conf); + g_test_lg = htrace_log_alloc(g_test_conf); + EXPECT_NONNULL(g_test_lg); + g_test_tracer = htracer_create("sampler-unit", g_test_conf); + EXPECT_NONNULL(g_test_tracer); + + EXPECT_INT_ZERO(test_unconfigured_sampler()); + EXPECT_INT_ZERO(test_never_sampler()); + EXPECT_INT_ZERO(test_always_sampler()); + EXPECT_INT_ZERO(test_prob_sampler(0.5, 0.001)); + EXPECT_INT_ZERO(test_prob_sampler(0.01, 0.001)); + EXPECT_INT_ZERO(test_prob_sampler(0.1, 0.001)); + + htracer_free(g_test_tracer); + htrace_log_free(g_test_lg); + htrace_conf_free(g_test_conf); + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/span-unit.c b/htrace-c/src/test/span-unit.c new file mode 100644 index 0000000..da6ca66 --- /dev/null +++ b/htrace-c/src/test/span-unit.c @@ -0,0 +1,173 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/conf.h" +#include "core/htrace.h" +#include "core/htracer.h" +#include "core/span.h" +#include "sampler/sampler.h" +#include "test/span_util.h" +#include "test/test.h" +#include "util/htable.h" +#include "util/log.h" + +#include <errno.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MAX_SPAN_JSON_LEN 100000 + +static struct htrace_conf *g_test_conf; + +static struct htrace_log *g_test_lg; + +static struct htracer *g_test_tracer; + +static struct htrace_span *create_span(const char *desc, + uint64_t begin_ms, uint64_t end_ms, uint64_t span_id, + const char *prid, ...) __attribute__((sentinel)); + +static int test_span_to_json(const char *expected, + struct htrace_span *span) +{ + int buf_len; + char *buf; + struct htrace_span *rspan = NULL; + char err[128]; + size_t err_len = sizeof(err); + + htrace_span_sort_and_dedupe_parents(span); + buf_len = span_json_size(span); + if ((0 > buf_len) || (buf_len > MAX_SPAN_JSON_LEN)) { + fprintf(stderr, "invalid span_json_size %d.\n", buf_len); + return EXIT_FAILURE; + } + buf = malloc(buf_len); + EXPECT_NONNULL(buf); + span_json_sprintf(span, buf_len, buf); + EXPECT_STR_EQ(expected, buf); + span_json_parse(buf, &rspan, err, err_len); + if (err[0]) { + fprintf(stderr, "Failed to parse span json %s: %s\n", buf, err); + return EXIT_FAILURE; + } + EXPECT_NONNULL(rspan); + if (span_compare(span, rspan) != 0) { + htrace_span_free(rspan); + fprintf(stderr, "Failed to parse the span json back into a span " + "which was identical to the input. JSON: %s\n", buf); + return EXIT_FAILURE; + } + free(buf); + htrace_span_free(rspan); + return EXIT_SUCCESS; +} + +static struct htrace_span *create_span(const char *desc, + uint64_t begin_ms, uint64_t end_ms, uint64_t span_id, + const char *prid, ...) +{ + struct htrace_span* span = NULL; + uint64_t *parents, parent; + int i, num_parents = 0; + va_list ap, ap2; + + va_start(ap, prid); + va_copy(ap2, ap); + while (1) { + parent = va_arg(ap2, uint64_t); + if (!parent) { + break; + } + num_parents++; + } while (parent); + va_end(ap2); + if (num_parents > 0) { + parents = xcalloc(sizeof(uint64_t) * num_parents); + for (i = 0; i < num_parents; i++) { + parents[i] = va_arg(ap, uint64_t); + } + } + va_end(ap); + span = htrace_span_alloc(desc, begin_ms, span_id); + span->end_ms = end_ms; + span->span_id = span_id; + span->prid = xstrdup(prid); + span->num_parents = num_parents; + if (num_parents == 1) { + span->parent.single = parents[0]; + free(parents); + } else if (num_parents > 1) { + span->parent.list = parents; + } + return span; +} + +static int test_spans_to_str(void) +{ + struct htrace_span *span; + + span = create_span("foo", 123LLU, 456LLU, 789LLU, "span-unit", + NULL); + EXPECT_INT_ZERO(test_span_to_json( + "{\"s\":\"0000000000000315\",\"b\":123,\"e\":456," + "\"d\":\"foo\",\"r\":\"span-unit\"" + ",\"p\":[]}", span)); + htrace_span_free(span); + + span = create_span("myspan", 34359738368LLU, + 34359739368LLU, 68719476736LLU, "span-unit2", + 1LLU, 2LLU, 3LLU, NULL); + EXPECT_INT_ZERO(test_span_to_json( + "{\"s\":\"0000001000000000\",\"b\":34359738368,\"e\":34359739368," + "\"d\":\"myspan\",\"r\":\"span-unit2\"," "\"p\":[\"0000000000000001\"," + "\"0000000000000002\",\"0000000000000003\"]}", span)); + htrace_span_free(span); + + span = create_span("nextSpan", 14359739368LLU, 18719476736LLU, + 0x8000001000000000LLU, "span-unit3", + 1LLU, 1LLU, 1LLU, NULL); + EXPECT_INT_ZERO(test_span_to_json( + "{\"s\":\"8000001000000000\",\"b\":14359739368,\"e\":18719476736," + "\"d\":\"nextSpan\",\"r\":\"span-unit3\"," "\"p\":[\"0000000000000001\"]" + "}", span)); + htrace_span_free(span); + return EXIT_SUCCESS; +} + +int main(void) +{ + g_test_conf = htrace_conf_from_strs("", HTRACE_PROCESS_ID"=span-unit"); + EXPECT_NONNULL(g_test_conf); + g_test_lg = htrace_log_alloc(g_test_conf); + EXPECT_NONNULL(g_test_lg); + g_test_tracer = htracer_create("span-unit", g_test_conf); + EXPECT_NONNULL(g_test_tracer); + + EXPECT_INT_ZERO(test_spans_to_str()); + + htracer_free(g_test_tracer); + htrace_log_free(g_test_lg); + htrace_conf_free(g_test_conf); + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_table.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/span_table.c b/htrace-c/src/test/span_table.c new file mode 100644 index 0000000..8840fbf --- /dev/null +++ b/htrace-c/src/test/span_table.c @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/span.h" +#include "test/span_table.h" +#include "test/span_util.h" +#include "test/test.h" +#include "util/htable.h" +#include "util/log.h" + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct span_table *span_table_alloc(void) +{ + struct htable *ht; + + ht = htable_alloc(128, ht_hash_string, ht_compare_string); + if (!ht) { + return NULL; + } + return (struct span_table*)ht; +} + +int span_table_get(struct span_table *st, struct htrace_span **out, + const char *desc, const char *prid) +{ + struct htable *ht = (struct htable *)st; + struct htrace_span *span; + + span = htable_get(ht, desc); + EXPECT_NONNULL(span); + EXPECT_STR_EQ(desc, span->desc); + EXPECT_UINT64_GE(span->begin_ms, span->end_ms); + EXPECT_UINT64_GT(0L, span->span_id); + EXPECT_NONNULL(span->prid); + EXPECT_STR_EQ(prid, span->prid); + *out = span; + return EXIT_SUCCESS; +} + +int span_table_put(struct span_table *st, struct htrace_span *span) +{ + struct htable *ht = (struct htable *)st; + int res; + + res = htable_put(ht, span->desc, span); + if (res) { + htrace_span_free(span); + return res; + } + return 0; +} + +static void span_table_free_entry(void *tracer, void *key, void *val) +{ + struct htrace_span *span = val; + htrace_span_free(span); + // We don't need to free the key, since it's simply the span->desc string, + // which is already freed by htrace_span_free. +} + +void span_table_free(struct span_table *st) +{ + struct htable *ht = (struct htable *)st; + + // Free all entries + htable_visit(ht, span_table_free_entry, NULL); + // Free the table itself + htable_free(ht); +} + +uint32_t span_table_size(struct span_table *st) +{ + struct htable *ht = (struct htable *)st; + return htable_used(ht); +} + +int load_trace_span_file(const char *path, struct span_table *st) +{ + char line[8196], err[1024]; + size_t err_len = sizeof(err); + FILE *fp = NULL; + int lineno = 0, ret = EXIT_FAILURE; + struct htrace_span *span; + + fp = fopen(path, "r"); + if (!fp) { + int res = errno; + fprintf(stderr, "failed to open %s: %s\n", path, terror(res)); + return -1; + } + while (1) { + ++lineno; + if (!fgets(line, sizeof(line), fp)) { + if (ferror(fp)) { + int res = errno; + fprintf(stderr, "error reading from %s: %s\n", + path, terror(res)); + break; + } + ret = EXIT_SUCCESS; + break; + } + span_json_parse(line, &span, err, err_len); + if (err[0]) { + fprintf(stderr, "error parsing line %d. Failed to parse %s " + "from %s: %s\n", lineno, line, path, err); + break; + } + span_table_put(st, span); + } + fclose(fp); + if (ret == EXIT_SUCCESS) { + //fprintf(stderr, "loaded %d spans from %s\n", lineno - 1, path); + return lineno - 1; + } + return -1; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_table.h ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/span_table.h b/htrace-c/src/test/span_table.h new file mode 100644 index 0000000..b55cd92 --- /dev/null +++ b/htrace-c/src/test/span_table.h @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APACHE_HTRACE_TEST_SPAN_TABLE_H +#define APACHE_HTRACE_TEST_SPAN_TABLE_H + +/** + * @file span_table.h + * + * Implements a hash table containing trace spans. They are indexed by span + * description. + * + * This is an internal header, not intended for external use. + */ + +struct htrace_span; +struct span_table; + +/** + * Allocate a span table. + * + * @return NULL on OOM; the span table otherwise. + */ +struct span_table *span_table_alloc(void); + +/** + * Retrieve a span from the table. + * + * @param st The span table. + * @param out (out param) the span. This pointer will be valid until + * the span table is freed. + * @param desc The span description to look for. + * @param prid The process ID to verify that the span has. + * + * @return 0 on success; nonzero otherwise. + */ +int span_table_get(struct span_table *st, struct htrace_span **out, + const char *desc, const char *prid); + +/** + * Add a span to the table. + * + * @param st The span table. + * @param span The span to add. Its memory will be managed by the + * span table after span_table_put is called. + * + * @return 0 on success; nonzero otherwise. + */ +int span_table_put(struct span_table *st, struct htrace_span *span); + +/** + * Free a span table. All spans inside will be freed. + * + * @param st The span table. + */ +void span_table_free(struct span_table *st); + +/** + * Get the size of the span table. + * + * @return The number of entries in the span table. + */ +uint32_t span_table_size(struct span_table *st); + +/** + * Load a file with newline-separated trace spans in JSON format into a span + * table. Note that this function assumes that every line contains a complete + * span, and that each line is less than 8196 bytes. + * + * @param path The path to read the file from. + * @param st The span table we will fill in. + * + * @return Negative numbers on failure; the number of lines we + * read otherwise. + */ +int load_trace_span_file(const char *path, struct span_table *st); + +#endif + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_util-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/span_util-unit.c b/htrace-c/src/test/span_util-unit.c new file mode 100644 index 0000000..fc2a56c --- /dev/null +++ b/htrace-c/src/test/span_util-unit.c @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/span_util.h" +#include "test/test.h" + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int test_parse_hex_id_error(const char *in) +{ + char err[128]; + + err[0] = '\0'; + parse_hex_id(in, err, sizeof(err)); + if (!err[0]) { + fprintf(stderr, "test_parse_hex_id_error(%s): expected error, but " + "was successful.\n", in); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static int test_parse_hex_id(uint64_t expected, const char *in) +{ + char err[128]; + size_t err_len = sizeof(err); + uint64_t val; + + err[0] = '\0'; + val = parse_hex_id(in, err, err_len); + if (err[0]) { + fprintf(stderr, "test_parse_hex_id(%s): got error %s\n", + in, err); + return EXIT_FAILURE; + } + EXPECT_UINT64_EQ(expected, val); + return EXIT_SUCCESS; +} + +int main(void) +{ + EXPECT_INT_ZERO(test_parse_hex_id_error("")); + EXPECT_INT_ZERO(test_parse_hex_id_error("z")); + EXPECT_INT_ZERO(test_parse_hex_id_error("achoo")); + EXPECT_INT_ZERO(test_parse_hex_id(1LLU, "00000000000000001")); + EXPECT_INT_ZERO(test_parse_hex_id(0xffffffffffffffffLLU, + "ffffffffffffffff")); + EXPECT_INT_ZERO(test_parse_hex_id(0x8000000000000000LLU, + "8000000000000000")); + EXPECT_INT_ZERO(test_parse_hex_id(0x6297421fe159345fLLU, + "6297421fe159345f")); + EXPECT_INT_ZERO(test_parse_hex_id_error("6297421fe159345fzoo")); + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_util.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/span_util.c b/htrace-c/src/test/span_util.c new file mode 100644 index 0000000..fc4b041 --- /dev/null +++ b/htrace-c/src/test/span_util.c @@ -0,0 +1,294 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/span.h" +#include "test/span_util.h" +#include "util/log.h" + +#include <errno.h> +#include <json/json_object.h> +#include <json/json_tokener.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +uint64_t parse_hex_id(const char *in, char *err, size_t err_len) +{ + char *endptr; + unsigned long long int ret; + + err[0] = '\0'; + errno = 0; + ret = strtoull(in, &endptr, 16); + if (errno) { + int e = errno; + snprintf(err, err_len, "parse_hex_id(%s) failed: error %s", + in, terror(e)); + return 0; + } + if (endptr == in) { + snprintf(err, err_len, "parse_hex_id(%s) failed: empty string " + "found.", in); + return 0; + } + while (1) { + char c = *endptr++; + if (c == '\0') { + break; + } + if ((c != ' ') || (c != '\t')) { + snprintf(err, err_len, "parse_hex_id(%s) failed: garbage at end " + "of string.", in); + return 0; + } + } + return ret; +} + +static void span_json_parse_parents(struct json_object *root, + struct htrace_span *span, char *err, size_t err_len) +{ + char err2[128]; + struct json_object *p = NULL, *e = NULL; + int i, np; + + if (!json_object_object_get_ex(root, "p", &p)) { + return; // no parents + } + if (json_object_get_type(p) != json_type_array) { + snprintf(err, err_len, "p element was not an array.."); + return; + } + np = json_object_array_length(p); + if (np == 1) { + span->num_parents = 1; + e = json_object_array_get_idx(p, 0); + span->parent.single = parse_hex_id(json_object_get_string(e), + err2, sizeof(err2)); + if (err2[0]) { + snprintf(err, err_len, "failed to parse parent ID 1/1: %s.", err2); + return; + } + } else if (np > 1) { + span->parent.list = malloc(sizeof(uint64_t) * np); + if (!span->parent.list) { + snprintf(err, err_len, "failed to allocate parent ID array of " + "%d elements", np); + return; + } + span->num_parents = np; + for (i = 0; i < np; i++) { + e = json_object_array_get_idx(p, i); + span->parent.list[i] = parse_hex_id(json_object_get_string(e), + err2, sizeof(err2)); + if (err2[0]) { + snprintf(err, err_len, "failed to parse parent ID %d/%d: %s", + i + 1, np, err2); + return; + } + } + } +} + +static void span_json_parse_impl(struct json_object *root, + struct htrace_span *span, char *err, size_t err_len) +{ + char err2[128]; + struct json_object *d = NULL, *b = NULL, *e = NULL, *s = NULL, *r = NULL; + int res; + + err[0] = '\0'; + if (!json_object_object_get_ex(root, "d", &d)) { + d = NULL; + } + span->desc = strdup(d ? json_object_get_string(d) : ""); + if (!span->desc) { + snprintf(err, err_len, "out of memory allocating description"); + return; + } + if (json_object_object_get_ex(root, "b", &b)) { + errno = 0; + span->begin_ms = json_object_get_int64(b); + res = errno; + if (res) { + snprintf(err, err_len, "error parsing begin_ms: %s", terror(res)); + return; + } + } + if (json_object_object_get_ex(root, "e", &e)) { + errno = 0; + span->end_ms = json_object_get_int64(e); + res = errno; + if (res) { + snprintf(err, err_len, "error parsing end_ms: %s", terror(res)); + return; + } + } + if (json_object_object_get_ex(root, "s", &s)) { + span->span_id = parse_hex_id(json_object_get_string(s), + err2, sizeof(err2)); + if (err2[0]) { + snprintf(err, err_len, "error parsing span_id: %s", err2); + return; + } + } + if (json_object_object_get_ex(root, "r", &r)) { + span->prid = strdup(json_object_get_string(r)); + } else { + span->prid = strdup(""); + } + if (!span->prid) { + snprintf(err, err_len, "out of memory allocating process id"); + return; + } + span_json_parse_parents(root, span, err, err_len); + if (err[0]) { + return; + } +} + +void span_json_parse(const char *in, struct htrace_span **rspan, + char *err, size_t err_len) +{ + struct json_object *root = NULL; + enum json_tokener_error jerr; + struct htrace_span *span = NULL; + + err[0] = '\0'; + root = json_tokener_parse_verbose(in, &jerr); + if (!root) { + snprintf(err, err_len, "json_tokener_parse_verbose failed: %s", + json_tokener_error_desc(jerr)); + goto done; + } + span = calloc(1, sizeof(*span)); + if (!span) { + snprintf(err, err_len, "failed to malloc span."); + goto done; + } + span_json_parse_impl(root, span, err, err_len); + +done: + if (root) { + json_object_put(root); + } + if (err[0]) { + htrace_span_free(span); + *rspan = NULL; + } else { + *rspan = span; + } +} + +/** + * Compare two 64-bit numbers. + * + * We don't use subtraction here in order to avoid numeric overflow. + */ +static int uint64_cmp(uint64_t a, uint64_t b) +{ + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } +} + +static int strcmp_handle_null(const char *a, const char *b) +{ + if (a == NULL) { + a = ""; + } + if (b == NULL) { + b = ""; + } + return strcmp(a, b); +} + +static int compare_parents(struct htrace_span *a, struct htrace_span *b) +{ + int na, nb, i; + + htrace_span_sort_and_dedupe_parents(a); + na = a->num_parents; + htrace_span_sort_and_dedupe_parents(b); + nb = b->num_parents; + + for (i = 0; ; i++) { + uint64_t sa, sb; + + if (i >= na) { + if (i >= nb) { + return 0; + } else { + return -1; + } + } else if (i >= nb) { + return 1; + } + if ((i == 0) && (na == 1)) { + sa = a->parent.single; + } else { + sa = a->parent.list[i]; + } + if ((i == 0) && (nb == 1)) { + sb = b->parent.single; + } else { + sb = b->parent.list[i]; + } + // Use explicit comparison rather than subtraction to avoid numeric + // overflow issues. + if (sa < sb) { + return -1; + } else if (sa > sb) { + return 1; + } + } +} + +int span_compare(struct htrace_span *a, struct htrace_span *b) +{ + int c; + + c = uint64_cmp(a->span_id, b->span_id); + if (c) { + return c; + } + c = strcmp(a->desc, b->desc); + if (c) { + return c; + } + c = uint64_cmp(a->begin_ms, b->begin_ms); + if (c) { + return c; + } + c = uint64_cmp(a->end_ms, b->end_ms); + if (c) { + return c; + } + c = strcmp_handle_null(a->prid, b->prid); + if (c) { + return c; + } + return compare_parents(a, b); +} + +// vim:ts=4:sw=4:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/span_util.h ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/span_util.h b/htrace-c/src/test/span_util.h new file mode 100644 index 0000000..393e213 --- /dev/null +++ b/htrace-c/src/test/span_util.h @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APACHE_HTRACE_TEST_SPAN_UTIL_H +#define APACHE_HTRACE_TEST_SPAN_UTIL_H + +#include <stdint.h> +#include <unistd.h> /* for size_t */ + +struct htrace_span; + +/** + * Parses 64-bit hex ID. + * + * @param in The hex ID string. + * @param err (out param) On error, where the error message will be + * written. Will be set to the empty string on success. + * @param err_len The length of the error buffer. Must be nonzero. + */ +uint64_t parse_hex_id(const char *in, char *err, size_t err_len); + +/** + * Parses a span JSON entry back into a span. + * + * This function is just used in unit tests and is not optimized. + * + * @param in The span string. + * @param span (out param) On success, the dynamically allocated span + * object. + * @param err (out param) On error, where the error message will be + * written. Will be set to the empty string on success. + * @param err_len The length of the error buffer. Must be nonzero. + */ +void span_json_parse(const char *in, struct htrace_span **span, + char *err, size_t err_len); + +/** + * Compare two spans. + * + * @param a The first span. + * @param b The second span. + * + * @return A negative number if the first span is less; + * 0 if the spans are equivalent; + * A positive number if the first span is greater. + */ +int span_compare(struct htrace_span *a, struct htrace_span *b); + +#endif + +// vim:ts=4:sw=4:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/string-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/string-unit.c b/htrace-c/src/test/string-unit.c new file mode 100644 index 0000000..56155ea --- /dev/null +++ b/htrace-c/src/test/string-unit.c @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/test.h" +#include "util/string.h" + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#pragma GCC diagnostic ignored "-Wformat-zero-length" + +static int test_fwdprintf(void) +{ + char *b, buf[8]; + int rem = sizeof(buf); + + memset(&buf, 0, sizeof(buf)); + b = buf; + EXPECT_INT_EQ(3, fwdprintf(NULL, NULL, "ab%c", 'c')); + EXPECT_INT_EQ(3, fwdprintf(&b, &rem, "ab%c", 'c')); + EXPECT_STR_EQ("abc", buf); + EXPECT_INT_EQ(5, rem); + EXPECT_INT_EQ(0, fwdprintf(NULL, NULL, "")); + EXPECT_INT_EQ(0, fwdprintf(&b, &rem, "")); + EXPECT_INT_EQ(5, rem); + EXPECT_INT_EQ(2, fwdprintf(NULL, NULL, "de")); + EXPECT_INT_EQ(2, fwdprintf(&b, &rem, "de")); + EXPECT_STR_EQ("abcde", buf); + EXPECT_INT_EQ(3, rem); + EXPECT_INT_EQ(6, fwdprintf(NULL, NULL, "fghijk")); + EXPECT_INT_EQ(6, fwdprintf(&b, &rem, "fghijk")); + EXPECT_INT_EQ(0, rem); + EXPECT_STR_EQ("abcdefg", buf); + return EXIT_SUCCESS; +} + +static int test_validate_json_string(void) +{ + EXPECT_INT_EQ(1, validate_json_string(NULL, "")); + EXPECT_INT_EQ(1, validate_json_string(NULL, "abc")); + EXPECT_INT_EQ(0, validate_json_string(NULL, "\\")); + EXPECT_INT_EQ(0, validate_json_string(NULL, "\"FooBar\"")); + EXPECT_INT_EQ(1, validate_json_string(NULL, "Foo:bar:baz-whatever")); + EXPECT_INT_EQ(0, validate_json_string(NULL, "\x01")); + return EXIT_SUCCESS; +} + +int main(void) +{ + EXPECT_INT_ZERO(test_fwdprintf()); + EXPECT_INT_ZERO(test_validate_json_string()); + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/temp_dir-unit.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/temp_dir-unit.c b/htrace-c/src/test/temp_dir-unit.c new file mode 100644 index 0000000..1c04dae --- /dev/null +++ b/htrace-c/src/test/temp_dir-unit.c @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/temp_dir.h" +#include "test/test.h" + +#include <limits.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +static int test_create_tempdir(void) +{ + char *tdir = NULL; + struct stat st_buf; + char err[128]; + size_t err_len = sizeof(err); + int ret; + + tdir = create_tempdir("test_create_tempdir", 0775, + err, err_len); + EXPECT_STR_EQ("", err); + ret = register_tempdir_for_cleanup(tdir); + if (ret) + return EXIT_FAILURE; + if (stat(tdir, &st_buf) == -1) { + return EXIT_FAILURE; + } + if (!S_ISDIR(st_buf.st_mode)) { + return EXIT_FAILURE; + } + free(tdir); + return 0; +} + +static int test_create_tempdir_and_delete(void) +{ + char *tdir = NULL; + struct stat st_buf; + int ret; + char err[128]; + size_t err_len = sizeof(err); + + tdir = create_tempdir("test_create_tempdir_and_delete", 0775, + err, err_len); + EXPECT_STR_EQ("", err); + ret = register_tempdir_for_cleanup(tdir); + if (ret) + return EXIT_FAILURE; + if (stat(tdir, &st_buf) == -1) { + return EXIT_FAILURE; + } + if (!S_ISDIR(st_buf.st_mode)) { + return EXIT_FAILURE; + } + recursive_unlink(tdir); + unregister_tempdir_for_cleanup(tdir); + free(tdir); + return 0; +} + +int main(void) +{ + EXPECT_INT_ZERO(test_create_tempdir()); + EXPECT_INT_ZERO(test_create_tempdir()); + EXPECT_INT_ZERO(test_create_tempdir()); + EXPECT_INT_ZERO(test_create_tempdir_and_delete()); + EXPECT_INT_ZERO(test_create_tempdir_and_delete()); + EXPECT_INT_ZERO(test_create_tempdir_and_delete()); + return EXIT_SUCCESS; +} + +// vim: ts=4:sw=4:tw=79:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/temp_dir.c ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/temp_dir.c b/htrace-c/src/test/temp_dir.c new file mode 100644 index 0000000..b11c8b6 --- /dev/null +++ b/htrace-c/src/test/temp_dir.c @@ -0,0 +1,204 @@ +/* + * Copyright 2011-2012 the Redfish authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/temp_dir.h" +#include "util/log.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +// Globals +static int g_tempdir_nonce = 0; + +static int g_num_tempdirs = 0; + +static char **g_tempdirs = NULL; + +static pthread_mutex_t tempdir_lock = PTHREAD_MUTEX_INITIALIZER; + +// Functions +static void cleanup_registered_tempdirs(void) +{ + int i; + const char *skip_cleanup; + skip_cleanup = getenv("SKIP_CLEANUP"); + if (skip_cleanup) + return; + pthread_mutex_lock(&tempdir_lock); + for (i = 0; i < g_num_tempdirs; ++i) { + recursive_unlink(g_tempdirs[i]); + free(g_tempdirs[i]); + } + free(g_tempdirs); + g_tempdirs = NULL; + pthread_mutex_unlock(&tempdir_lock); +} + +char *create_tempdir(const char *name, int mode, char *err, size_t err_len) +{ + char *tdir = NULL, tmp[PATH_MAX]; + int nonce, pid; + const char *base = getenv("TMPDIR"); + + err[0] = '\0'; + if (!base) { + base = "/tmp"; + } + if (base[0] != '/') { + // canonicalize non-abosolute TMPDIR + if (realpath(base, tmp) == NULL) { + int e = errno; + snprintf(err, err_len, "realpath(%s) failed: %s", + base, terror(e)); + return NULL; + } + base = tmp; + } + pthread_mutex_lock(&tempdir_lock); + nonce = g_tempdir_nonce++; + pthread_mutex_unlock(&tempdir_lock); + pid = getpid(); + if (asprintf(&tdir, "%s/%s.tmp.%08d.%08d", + base, name, pid, nonce) < 0) { + snprintf(err, err_len, "asprintf failed"); + return NULL; + } + if (mkdir(tdir, mode) == -1) { + int e = errno; + snprintf(err, err_len, "mkdir(%s) failed: %s", tdir, terror(e)); + free(tdir); + return NULL; + } + return tdir; +} + +int register_tempdir_for_cleanup(const char *tdir) +{ + char **tempdirs; + pthread_mutex_lock(&tempdir_lock); + tempdirs = realloc(g_tempdirs, sizeof(char*) * (g_num_tempdirs + 1)); + if (!tempdirs) + return -ENOMEM; + g_tempdirs = tempdirs; + g_tempdirs[g_num_tempdirs] = strdup(tdir); + if (g_num_tempdirs == 0) + atexit(cleanup_registered_tempdirs); + g_num_tempdirs++; + pthread_mutex_unlock(&tempdir_lock); + return 0; +} + +void unregister_tempdir_for_cleanup(const char *tdir) +{ + int i; + char **tempdirs; + pthread_mutex_lock(&tempdir_lock); + if (g_num_tempdirs == 0) { + pthread_mutex_unlock(&tempdir_lock); + return; + } + for (i = 0; i < g_num_tempdirs; ++i) { + if (strcmp(g_tempdirs[i], tdir) == 0) + break; + } + if (i == g_num_tempdirs) { + pthread_mutex_unlock(&tempdir_lock); + return; + } + free(g_tempdirs[i]); + g_tempdirs[i] = g_tempdirs[g_num_tempdirs - 1]; + tempdirs = realloc(g_tempdirs, sizeof(char*) * g_num_tempdirs - 1); + if (tempdirs) { + g_tempdirs = tempdirs; + } + g_num_tempdirs--; + pthread_mutex_unlock(&tempdir_lock); +} + +static int recursive_unlink_helper(int dirfd, const char *name) +{ + int fd = -1, ret = 0; + DIR *dfd = NULL; + struct stat stat; + struct dirent *de; + + if (dirfd >= 0) { + fd = openat(dirfd, name, O_RDONLY); + } else { + fd = open(name, O_RDONLY); + } + if (fd < 0) { + ret = errno; + fprintf(stderr, "error opening %s: %s\n", name, terror(ret)); + goto done; + } + if (fstat(fd, &stat) < 0) { + ret = errno; + fprintf(stderr, "failed to stat %s: %s\n", name, terror(ret)); + goto done; + } + if (!(S_ISDIR(stat.st_mode))) { + if (unlinkat(dirfd, name, 0)) { + ret = errno; + fprintf(stderr, "failed to unlink %s: %s\n", name, terror(ret)); + goto done; + } + } else { + dfd = fdopendir(fd); + if (!dfd) { + ret = errno; + fprintf(stderr, "fopendir(%s) failed: %s\n", name, terror(ret)); + goto done; + } + while ((de = readdir(dfd))) { + if (!strcmp(de->d_name, ".")) + continue; + if (!strcmp(de->d_name, "..")) + continue; + ret = recursive_unlink_helper(fd, de->d_name); + if (ret) + goto done; + } + if (unlinkat(dirfd, name, AT_REMOVEDIR) < 0) { + ret = errno; + goto done; + } + } +done: + if (fd >= 0) { + close(fd); + } + if (dfd) { + closedir(dfd); + } + return -ret; +} + +int recursive_unlink(const char *path) +{ + return recursive_unlink_helper(-1, path); +} + +// vim:ts=4:sw=4:et http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/a9d85254/htrace-c/src/test/temp_dir.h ---------------------------------------------------------------------- diff --git a/htrace-c/src/test/temp_dir.h b/htrace-c/src/test/temp_dir.h new file mode 100644 index 0000000..d12e961 --- /dev/null +++ b/htrace-c/src/test/temp_dir.h @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef APACHE_HTRACE_TEST_TEMP_DIR_H +#define APACHE_HTRACE_TEST_TEMP_DIR_H + +#include <unistd.h> /* for size_t */ + +/** + * Create a temporary directory + * + * @param tempdir The identifier to use for this temporary directory. We will + * add a random string to this identifier to create the + * full path. + * @param mode The mode to use in mkdir. Typically 0755. + * @param err The error buffer to be set on failure. + * @param err_len Length of the error buffer. + * + * @return A malloc'ed string with the path to the temporary directory, + * or NULL on failure. + */ +char *create_tempdir(const char *name, int mode, char *err, size_t err_len); + +/** + * Register a temporary directory to be deleted at the end of the program + * + * @param tempdir The path of the temporary directory to register. The string + * will be deep-copied. + * + * @return 0 on success; error code otherwise + */ +int register_tempdir_for_cleanup(const char *tempdir); + +/** + * Unregister a temporary directory to be deleted at the end of the program + * + * @param tempdir The path of the temporary directory to unregister. + */ +void unregister_tempdir_for_cleanup(const char *tempdir); + +/** + * Recursively unlink a directory. + * + * @param tempdir The directory to remove. + * + * @return Zero on success; the error code otherwise. + */ +int recursive_unlink(const char *path); + +#endif + +// vim:ts=4:sw=4:et
