You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by cm...@apache.org on 2015/04/20 02:50:20 UTC

[2/4] incubator-htrace git commit: HTRACE-106: htrace: add C / C++ native client (cmccabe)

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