aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2025-12-31 11:51:35 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2025-12-31 11:53:20 -0500
commit1d071010c2bec860e62371cfb70f2e12a7d00563 (patch)
tree53b3a077f9cbd7d41d530cb7d54eb03716a97633 /src
parent059519c39bb7d452d651e7dc440594251966c88b (diff)
downloadstasis-1d071010c2bec860e62371cfb70f2e12a7d00563.tar.gz
Update multiprocessing to use semaphoresemaphore
* Introduce a small public domain timespec handling library * Renamed MultiProcessingTask members _now and _seconds to _interval_start and _interval_stop * Added interval_elapsed member * Change elapsed member from type int to double * mp_pool_free no longer tries to free semaphores with empty names * semaphore_init uses the correct default value of 1 instead of 2. The bug was related to calling semaphore_wait before the child() process started and semaphore_post after the parent exited. Now we post before the child to prevent a deadlock * Replace sleep with usleep in mp_pool_join. Set to 100ms. * Replace time() with clock_gettime() (helper functions created to prevent mistakes) * Stop recording time when the process ends. This fixes a bug where a process ends and up to 3 different elapsed times were reported to the user. * Progress output is now always available, not only when pid > 0 * Implement seconds_to_human_readable and hook it up to progress reporting calls. Breaking down thousands of seconds in my head after a long run was mentally exhausting. * Cleaned up some if-statements; removed else-if for clarity * Implemented a global timeout for pool tasks * Add register_semaphore() and semaphore_handle_exit() to aid with clean up. On Darwin a dangling shared memory file will lead to unexpected failures. These are destroyed via atexit().
Diffstat (limited to 'src')
-rw-r--r--src/lib/core/CMakeLists.txt1
-rw-r--r--src/lib/core/include/multiprocessing.h9
-rw-r--r--src/lib/core/include/timespec.h71
-rw-r--r--src/lib/core/multiprocessing.c207
-rw-r--r--src/lib/core/semaphore.c35
-rw-r--r--src/lib/core/timespec.c979
6 files changed, 1233 insertions, 69 deletions
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 7b33cf9..eb7a908 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -1,5 +1,6 @@
add_library(stasis_core STATIC
globals.c
+ timespec.c
str.c
strlist.c
ini.c
diff --git a/src/lib/core/include/multiprocessing.h b/src/lib/core/include/multiprocessing.h
index ab7b416..933f7d6 100644
--- a/src/lib/core/include/multiprocessing.h
+++ b/src/lib/core/include/multiprocessing.h
@@ -4,11 +4,13 @@
#include "core.h"
#include "sem.h"
+#include "timespec.h"
#include <signal.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
+#include <math.h>
struct MultiProcessingTask {
pid_t pid; ///< Program PID
@@ -16,10 +18,11 @@ struct MultiProcessingTask {
int status; ///< Child process exit status
int signaled_by; ///< Last signal received, if any
int timeout; ///< Seconds to elapse before killing the process
- time_t _now; ///< Current time
- time_t _seconds; ///< Time elapsed since status interval (used by MultiprocessingPool.status_interval)
+ struct timespec _interval_start; ///< Current time, start of interval
+ struct timespec _interval_stop; ///< Time elapsed since status interval (used by MultiprocessingPool.status_interval)
+ double interval_elapsed;
time_t _startup; ///< Time elapsed since task started
- long elapsed; ///< Total time elapsed in seconds
+ double elapsed; ///< Total time elapsed in seconds
char ident[255]; ///< Identity of the pool task
char *cmd; ///< Shell command(s) to be executed
size_t cmd_len; ///< Length of command string (for mmap/munmap)
diff --git a/src/lib/core/include/timespec.h b/src/lib/core/include/timespec.h
new file mode 100644
index 0000000..3f4b9a7
--- /dev/null
+++ b/src/lib/core/include/timespec.h
@@ -0,0 +1,71 @@
+/* Functions for working with timespec structures
+ * Written by Daniel Collins (2017-2021)
+ * timespec_mod by Alex Forencich (2019)
+ * Various contributions by Ingo Albrecht (2021)
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to <http://unlicense.org/>
+*/
+
+#ifndef DAN_TIMESPEC_H
+#define DAN_TIMESPEC_H
+
+#include <stdbool.h>
+#include <sys/time.h>
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct timespec timespec_add(struct timespec ts1, struct timespec ts2);
+struct timespec timespec_sub(struct timespec ts1, struct timespec ts2);
+struct timespec timespec_mod(struct timespec ts1, struct timespec ts2);
+
+struct timespec timespec_min(struct timespec ts1, struct timespec ts2);
+struct timespec timespec_max(struct timespec ts1, struct timespec ts2);
+struct timespec timespec_clamp(struct timespec ts1, struct timespec min, struct timespec max);
+
+int timespec_cmp(struct timespec ts1, struct timespec ts2);
+bool timespec_eq(struct timespec ts1, struct timespec ts2);
+bool timespec_gt(struct timespec ts1, struct timespec ts2);
+bool timespec_ge(struct timespec ts1, struct timespec ts2);
+bool timespec_lt(struct timespec ts1, struct timespec ts2);
+bool timespec_le(struct timespec ts1, struct timespec ts2);
+
+struct timespec timespec_from_double(double s);
+double timespec_to_double(struct timespec ts);
+struct timespec timespec_from_timeval(struct timeval tv);
+struct timeval timespec_to_timeval(struct timespec ts);
+struct timespec timespec_from_ms(long milliseconds);
+long timespec_to_ms(struct timespec ts);
+
+struct timespec timespec_normalise(struct timespec ts);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !DAN_TIMESPEC_H */
diff --git a/src/lib/core/multiprocessing.c b/src/lib/core/multiprocessing.c
index ff4453c..43f3e07 100644
--- a/src/lib/core/multiprocessing.c
+++ b/src/lib/core/multiprocessing.c
@@ -4,6 +4,74 @@
/// The sum of all tasks started by mp_task()
size_t mp_global_task_count = 0;
+static char *seconds_to_human_readable(const int v) {
+ static char result[255] = {0};
+ const int hours = v / 3600;
+ const int minutes = (v % 3600) / 60;
+ const int seconds = v % 60;
+
+ memset(result, '\0', sizeof(result));
+ if (hours) {
+ snprintf(result + strlen(result), sizeof(result), "%dh ", hours);
+ }
+ if (minutes) {
+ snprintf(result + strlen(result), sizeof(result), "%dm ", minutes);
+ }
+ snprintf(result + strlen(result), sizeof(result), "%ds", seconds);
+
+ return result;
+}
+
+static double get_duration(const struct timespec stop, const struct timespec start) {
+ const struct timespec result = timespec_sub(stop, start);
+ return timespec_to_double(result);
+}
+
+static double get_task_duration(const struct MultiProcessingTask *task) {
+ const struct timespec *start = &task->time_data.t_start;
+ const struct timespec *stop = &task->time_data.t_stop;
+ return get_duration(*stop, *start);
+}
+
+static double get_task_interval_duration(const struct MultiProcessingTask *task) {
+ const struct timespec *start = &task->_interval_start;
+ const struct timespec *stop = &task->_interval_stop;
+ return get_duration(*stop, *start);
+}
+
+static void update_task_interval_start(struct MultiProcessingTask *task) {
+ // Record the task stop time
+ if (clock_gettime(CLOCK_REALTIME, &task->_interval_start) < 0) {
+ perror("clock_gettime");
+ exit(1);
+ }
+}
+
+static void update_task_interval_elapsed(struct MultiProcessingTask *task) {
+ // Record the interval stop time
+ if (clock_gettime(CLOCK_REALTIME, &task->_interval_stop) < 0) {
+ perror("clock_gettime");
+ exit(1);
+ }
+ task->interval_elapsed = get_task_interval_duration(task);
+}
+
+static void update_task_start(struct MultiProcessingTask *task) {
+ // Record the task start time
+ if (clock_gettime(CLOCK_REALTIME, &task->time_data.t_start) < 0) {
+ perror("clock_gettime");
+ exit(1);
+ }
+}
+static void update_task_elapsed(struct MultiProcessingTask *task) {
+ // Record the task stop time
+ if (clock_gettime(CLOCK_REALTIME, &task->time_data.t_stop) < 0) {
+ perror("clock_gettime");
+ exit(1);
+ }
+ task->elapsed = get_task_duration(task);
+}
+
static struct MultiProcessingTask *mp_pool_next_available(struct MultiProcessingPool *pool) {
return &pool->task[pool->num_used];
}
@@ -18,12 +86,6 @@ int child(struct MultiProcessingPool *pool, struct MultiProcessingTask *task) {
exit(1);
}
- // Record the task start time
- if (clock_gettime(CLOCK_REALTIME, &task->time_data.t_start) < 0) {
- perror("clock_gettime");
- exit(1);
- }
-
// Redirect stdout and stderr to the log file
fflush(stdout);
fflush(stderr);
@@ -64,13 +126,18 @@ int child(struct MultiProcessingPool *pool, struct MultiProcessingTask *task) {
}
int parent(struct MultiProcessingPool *pool, struct MultiProcessingTask *task, pid_t pid, int *child_status) {
+ // Record the task start time
+ update_task_start(task);
+
printf("[%s:%s] Task started (pid: %d)\n", pool->ident, task->ident, pid);
// Give the child process access to our PID value
task->pid = pid;
task->parent_pid = pid;
+ semaphore_wait(&pool->semaphore);
mp_global_task_count++;
+ semaphore_post(&pool->semaphore);
// Check child's status
pid_t code = waitpid(pid, child_status, WUNTRACED | WCONTINUED | WNOHANG);
@@ -91,13 +158,13 @@ static int mp_task_fork(struct MultiProcessingPool *pool, struct MultiProcessing
return -1;
}
if (pid == 0) {
+ semaphore_post(&pool->semaphore);
child(pool, task);
} else {
parent_status = parent(pool, task, pid, &child_status);
fflush(stdout);
fflush(stderr);
}
- semaphore_post(&pool->semaphore);
return parent_status;
}
@@ -173,24 +240,11 @@ struct MultiProcessingTask *mp_pool_task(struct MultiProcessingPool *pool, const
return slot;
}
-static void get_task_duration(struct MultiProcessingTask *task, struct timespec *result) {
- // based on the timersub() macro in time.h
- // This implementation uses timespec and increases the resolution from microseconds to nanoseconds.
- struct timespec *start = &task->time_data.t_start;
- struct timespec *stop = &task->time_data.t_stop;
- result->tv_sec = (stop->tv_sec - start->tv_sec);
- result->tv_nsec = (stop->tv_nsec - start->tv_nsec);
- if (result->tv_nsec < 0) {
- --result->tv_sec;
- result->tv_nsec += 1000000000L;
- }
-}
-
void mp_pool_show_summary(struct MultiProcessingPool *pool) {
print_banner("=", 79);
printf("Pool execution summary for \"%s\"\n", pool->ident);
print_banner("=", 79);
- printf("STATUS PID DURATION IDENT\n");
+ printf("STATUS PID DURATION IDENT\n");
for (size_t i = 0; i < pool->num_used; i++) {
struct MultiProcessingTask *task = &pool->task[i];
char status_str[10] = {0};
@@ -208,10 +262,8 @@ void mp_pool_show_summary(struct MultiProcessingPool *pool) {
strcpy(status_str, "FAIL");
}
- struct timespec duration;
- get_task_duration(task, &duration);
- long diff = duration.tv_sec + duration.tv_nsec / 1000000000L;
- printf("%-4s %10d %7lds %-10s\n", status_str, task->parent_pid, diff, task->ident) ;
+ printf("%-4s %10d %10s %-10s\n", status_str, task->parent_pid, seconds_to_human_readable(task->elapsed), task->ident) ;
+ //printf("%-4s %10d %7lds %-10s\n", status_str, task->parent_pid, task->elapsed, task->ident) ;
}
puts("");
}
@@ -243,18 +295,18 @@ int mp_pool_kill(struct MultiProcessingPool *pool, int signum) {
if (slot->pid > 0) {
int status;
printf("Sending signal %d to task '%s' (pid: %d)\n", signum, slot->ident, slot->pid);
+ semaphore_wait(&pool->semaphore);
status = kill(slot->pid, signum);
+ semaphore_post(&pool->semaphore);
if (status && errno != ESRCH) {
fprintf(stderr, "Task '%s' (pid: %d) did not respond: %s\n", slot->ident, slot->pid, strerror(errno));
} else {
// Wait for process to handle the signal, then set the status accordingly
if (waitpid(slot->pid, &status, 0) >= 0) {
slot->signaled_by = WTERMSIG(status);
- // Record the task stop time
- if (clock_gettime(CLOCK_REALTIME, &slot->time_data.t_stop) < 0) {
- perror("clock_gettime");
- exit(1);
- }
+ semaphore_wait(&pool->semaphore);
+ update_task_elapsed(slot);
+ semaphore_post(&pool->semaphore);
// We are short-circuiting the normal flow, and the process is now dead, so mark it as such
SYSDEBUG("Marking slot %zu: UNUSED", i);
slot->pid = MP_POOL_PID_UNUSED;
@@ -262,15 +314,20 @@ int mp_pool_kill(struct MultiProcessingPool *pool, int signum) {
}
}
if (globals.enable_task_logging) {
+ semaphore_wait(&pool->semaphore);
if (!access(slot->log_file, F_OK)) {
SYSDEBUG("Removing log file: %s", slot->log_file);
remove(slot->log_file);
}
+ semaphore_post(&pool->semaphore);
}
+
+ semaphore_wait(&pool->semaphore);
if (!access(slot->parent_script, F_OK)) {
SYSDEBUG("Removing runner script: %s", slot->parent_script);
remove(slot->parent_script);
}
+ semaphore_post(&pool->semaphore);
}
return 0;
}
@@ -317,32 +374,49 @@ int mp_pool_join(struct MultiProcessingPool *pool, size_t jobs, size_t flags) {
// Is the process finished?
pid_t pid = waitpid(slot->pid, &status, WNOHANG | WUNTRACED | WCONTINUED);
- int task_ended = WIFEXITED(status);
- int task_ended_by_signal = WIFSIGNALED(status);
- int task_stopped = WIFSTOPPED(status);
- int task_continued = WIFCONTINUED(status);
- int status_exit = WEXITSTATUS(status);
- int status_signal = WTERMSIG(status);
- int status_stopped = WSTOPSIG(status);
+
+ char progress[1024] = {0};
+ const double percent = ((double) (tasks_complete + 1) / (double) pool->num_used) * 100;
+ snprintf(progress, sizeof(progress) - 1, "[%s:%s] [%3.1f%%]", pool->ident, slot->ident, percent);
+
+ int task_timed_out = false;
+ if (slot->timeout) {
+ task_timed_out = slot->elapsed >= (double) slot->timeout;
+ if (task_timed_out && pid == 0 && slot->pid != 0) {
+ printf("%s Task timed out after %s (pid: %d)\n", progress, seconds_to_human_readable(slot->timeout), slot->pid);
+ if (kill(slot->pid, SIGKILL) == 0) {
+ status = SIGKILL;
+ } else {
+ SYSERROR("Timeout reached, however pid %d could not be killed.", slot->pid);
+ return -1;
+ }
+ }
+ }
+
+ const int task_ended = WIFEXITED(status);
+ const int task_ended_by_signal = WIFSIGNALED(status);
+ const int task_stopped = WIFSTOPPED(status);
+ const int task_continued = WIFCONTINUED(status);
+ const int status_exit = WEXITSTATUS(status);
+ const int status_signal = WTERMSIG(status);
+ const int status_stopped = WSTOPSIG(status);
// Update status
slot->status = status_exit;
slot->signaled_by = status_signal;
- char progress[1024] = {0};
if (pid > 0) {
- double percent = ((double) (tasks_complete + 1) / (double) pool->num_used) * 100;
- snprintf(progress, sizeof(progress) - 1, "[%s:%s] [%3.1f%%]", pool->ident, slot->ident, percent);
-
// The process ended in one the following ways
// Note: SIGSTOP nor SIGCONT will not increment the tasks_complete counter
if (task_stopped) {
printf("%s Task was suspended (%d)\n", progress, status_stopped);
continue;
- } else if (task_continued) {
+ }
+ if (task_continued) {
printf("%s Task was resumed\n", progress);
continue;
- } else if (task_ended_by_signal) {
+ }
+ if (task_ended_by_signal) {
printf("%s Task ended by signal %d (%s)\n", progress, status_signal, strsignal(status_signal));
tasks_complete++;
} else if (task_ended) {
@@ -359,14 +433,11 @@ int mp_pool_join(struct MultiProcessingPool *pool, size_t jobs, size_t flags) {
}
}
- // Record the task stop time
- if (clock_gettime(CLOCK_REALTIME, &slot->time_data.t_stop) < 0) {
- perror("clock_gettime");
- exit(1);
- }
-
if (status >> 8 != 0 || (status & 0xff) != 0) {
- fprintf(stderr, "%s Task failed after %lus\n", progress, slot->elapsed);
+ semaphore_wait(&pool->semaphore);
+ update_task_elapsed(slot);
+ semaphore_post(&pool->semaphore);
+ fprintf(stderr, "%s Task failed after %s\n", progress, seconds_to_human_readable(slot->elapsed));
failures++;
if (flags & MP_POOL_FAIL_FAST && pool->num_used > 1) {
@@ -374,7 +445,7 @@ int mp_pool_join(struct MultiProcessingPool *pool, size_t jobs, size_t flags) {
return -2;
}
} else {
- printf("%s Task finished after %lus\n", progress, slot->elapsed);
+ printf("%s Task finished after %s\n", progress, seconds_to_human_readable(slot->elapsed));
}
// Clean up logs and scripts left behind by the task
@@ -395,17 +466,25 @@ int mp_pool_join(struct MultiProcessingPool *pool, size_t jobs, size_t flags) {
} else {
// Track the number of seconds elapsed for each task.
// When a task has executed for longer than status_intervals, print a status update
- // _seconds represents the time between intervals, not the total runtime of the task
- slot->_seconds = time(NULL) - slot->_now;
- if (slot->_seconds > pool->status_interval) {
- slot->_now = time(NULL);
- slot->_seconds = 0;
+ // interval_elapsed represents the time between intervals, not the total runtime of the task
+ semaphore_wait(&pool->semaphore);
+ if (fabs(slot->interval_elapsed) > pool->status_interval) {
+ slot->interval_elapsed = 0.0;
}
- if (slot->_seconds == 0) {
- printf("[%s:%s] Task is running (pid: %d, elapsed: %lus)\n", pool->ident, slot->ident, slot->parent_pid, slot->elapsed);
+ if (slot->interval_elapsed == 0.0) {
+ printf("[%s:%s] Task is running (pid: %d, elapsed: %s)\n", pool->ident, slot->ident, slot->parent_pid, seconds_to_human_readable(slot->elapsed));
+ update_task_interval_start(slot);
}
+
+ update_task_interval_elapsed(slot);
+ semaphore_post(&pool->semaphore);
+ }
+
+ if (!task_ended || !task_ended_by_signal) {
+ semaphore_wait(&pool->semaphore);
+ update_task_elapsed(slot);
+ semaphore_post(&pool->semaphore);
}
- slot->elapsed = time(NULL) - slot->_startup;
}
if (tasks_complete == pool->num_used) {
@@ -418,7 +497,7 @@ int mp_pool_join(struct MultiProcessingPool *pool, size_t jobs, size_t flags) {
}
// Poll again after a short delay
- sleep(1);
+ usleep(100000);
} while (1);
pool_deadlocked:
@@ -469,19 +548,21 @@ struct MultiProcessingPool *mp_pool_init(const char *ident, const char *log_root
char semaphore_name[255] = {0};
snprintf(semaphore_name, sizeof(semaphore_name), "stasis_mp_%s", ident);
- if (semaphore_init(&pool->semaphore, semaphore_name, 2) != 0) {
+ if (semaphore_init(&pool->semaphore, semaphore_name, 1) != 0) {
fprintf(stderr, "unable to initialize semaphore\n");
mp_pool_free(&pool);
return NULL;
}
+ pool->status_interval = 3;
+
return pool;
}
void mp_pool_free(struct MultiProcessingPool **pool) {
- for (size_t i = 0; i < (*pool)->num_alloc; i++) {
+ if (!isempty((*pool)->semaphore.name)) {
+ semaphore_destroy(&(*pool)->semaphore);
}
- semaphore_destroy(&(*pool)->semaphore);
// Unmap all pool tasks
if ((*pool)->task) {
diff --git a/src/lib/core/semaphore.c b/src/lib/core/semaphore.c
index 5c0e904..579479a 100644
--- a/src/lib/core/semaphore.c
+++ b/src/lib/core/semaphore.c
@@ -8,6 +8,28 @@
#include "sem.h"
#include "utils.h"
+struct Semaphore *semaphores[1000] = {0};
+bool semaphore_handle_exit_ready = false;
+
+void semaphore_handle_exit() {
+ for (size_t i = 0; i < sizeof(semaphores) / sizeof(*semaphores); ++i) {
+ if (semaphores[i]) {
+ SYSDEBUG("%s", semaphores[i]->name);
+ semaphore_destroy(semaphores[i]);
+ }
+ }
+}
+
+static void register_semaphore(struct Semaphore *s) {
+ struct Semaphore **cur = semaphores;
+ size_t i = 0;
+ while (i < sizeof(semaphores) / sizeof(*semaphores) && cur != NULL) {
+ cur++;
+ i++;
+ }
+ cur = &s;
+}
+
int semaphore_init(struct Semaphore *s, const char *name, const int value) {
#if defined(STASIS_OS_DARWIN)
// see: sem_open(2)
@@ -22,21 +44,28 @@ int semaphore_init(struct Semaphore *s, const char *name, const int value) {
return -1;
}
SYSDEBUG("%s", s->name);
+ register_semaphore(s);
+ if (!semaphore_handle_exit_ready) {
+ atexit(semaphore_handle_exit);
+ }
+
return 0;
}
int semaphore_wait(struct Semaphore *s) {
- SYSDEBUG("%s", s->name);
return sem_wait(s->sem);
}
int semaphore_post(struct Semaphore *s) {
- SYSDEBUG("%s", s->name);
return sem_post(s->sem);
}
void semaphore_destroy(struct Semaphore *s) {
+ if (!s) {
+ SYSDEBUG("%s", "would have crashed");
+ return;
+ }
SYSDEBUG("%s", s->name);
sem_close(s->sem);
sem_unlink(s->name);
-}
+} \ No newline at end of file
diff --git a/src/lib/core/timespec.c b/src/lib/core/timespec.c
new file mode 100644
index 0000000..bd33993
--- /dev/null
+++ b/src/lib/core/timespec.c
@@ -0,0 +1,979 @@
+/* Functions for working with timespec structures
+ * Written by Daniel Collins (2017-2021)
+ * timespec_mod by Alex Forencich (2019)
+ * Various contributions by Ingo Albrecht (2021)
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to <http://unlicense.org/>
+*/
+
+/** \file timespec.c
+ * \brief Functions for working with timespec structures.
+ *
+ * This library aims to provide a comprehensive set of functions with
+ * well-defined behaviour that handle all edge cases (e.g. negative values) in
+ * a sensible manner.
+ *
+ * Negative values are allowed in the tv_sec and/or tv_usec field of timespec
+ * structures, tv_usec is always relative to tv_sec, so mixing positive and
+ * negative values will produce consistent results:
+ *
+ * <PRE>
+ * { tv_sec = 1, tv_nsec = 500000000 } == 1.5 seconds
+ * { tv_sec = 1, tv_nsec = 0 } == 1.0 seconds
+ * { tv_sec = 1, tv_nsec = -500000000 } == 0.5 seconds
+ * { tv_sec = 0, tv_nsec = 500000000 } == 0.5 seconds
+ * { tv_sec = 0, tv_nsec = 0 } == 0.0 seconds
+ * { tv_sec = 0, tv_nsec = -500000000 } == -0.5 seconds
+ * { tv_sec = -1, tv_nsec = 500000000 } == -0.5 seconds
+ * { tv_sec = -1, tv_nsec = 0 } == -1.0 seconds
+ * { tv_sec = -1, tv_nsec = -500000000 } == -1.5 seconds
+ * </PRE>
+ *
+ * Furthermore, any timespec structure processed or returned by library functions
+ * is normalised according to the rules in timespec_normalise().
+*/
+
+#include <limits.h>
+#include <stdbool.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "timespec.h"
+
+#define NSEC_PER_SEC 1000000000
+
+/** \fn struct timespec timespec_add(struct timespec ts1, struct timespec ts2)
+ * \brief Returns the result of adding two timespec structures.
+*/
+struct timespec timespec_add(struct timespec ts1, struct timespec ts2)
+{
+ /* Normalise inputs to prevent tv_nsec rollover if whole-second values
+ * are packed in it.
+ */
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ ts1.tv_sec += ts2.tv_sec;
+ ts1.tv_nsec += ts2.tv_nsec;
+
+ return timespec_normalise(ts1);
+}
+
+/** \fn struct timespec timespec_sub(struct timespec ts1, struct timespec ts2)
+ * \brief Returns the result of subtracting ts2 from ts1.
+*/
+struct timespec timespec_sub(struct timespec ts1, struct timespec ts2)
+{
+ /* Normalise inputs to prevent tv_nsec rollover if whole-second values
+ * are packed in it.
+ */
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ ts1.tv_sec -= ts2.tv_sec;
+ ts1.tv_nsec -= ts2.tv_nsec;
+
+ return timespec_normalise(ts1);
+}
+
+/** \fn struct timespec timespec_mod(struct timespec ts1, struct timespec ts2)
+ * \brief Returns the remainder left over after dividing ts1 by ts2 (ts1%ts2).
+*/
+struct timespec timespec_mod(struct timespec ts1, struct timespec ts2)
+{
+ int i = 0;
+ bool neg1 = false;
+ bool neg2 = false;
+
+ /* Normalise inputs to prevent tv_nsec rollover if whole-second values
+ * are packed in it.
+ */
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ /* If ts2 is zero, just return ts1
+ */
+ if (ts2.tv_sec == 0 && ts2.tv_nsec == 0)
+ {
+ return ts1;
+ }
+
+ /* If inputs are negative, flip and record sign
+ */
+ if (ts1.tv_sec < 0 || ts1.tv_nsec < 0)
+ {
+ neg1 = true;
+ ts1.tv_sec = -ts1.tv_sec;
+ ts1.tv_nsec = -ts1.tv_nsec;
+ }
+
+ if (ts2.tv_sec < 0 || ts2.tv_nsec < 0)
+ {
+ neg2 = true;
+ ts2.tv_sec = -ts2.tv_sec;
+ ts2.tv_nsec = -ts2.tv_nsec;
+ }
+
+ /* Shift ts2 until it is larger than ts1 or is about to overflow
+ */
+ while ((ts2.tv_sec < (LONG_MAX >> 1)) && timespec_ge(ts1, ts2))
+ {
+ i++;
+ ts2.tv_nsec <<= 1;
+ ts2.tv_sec <<= 1;
+ if (ts2.tv_nsec > NSEC_PER_SEC)
+ {
+ ts2.tv_nsec -= NSEC_PER_SEC;
+ ts2.tv_sec++;
+ }
+ }
+
+ /* Division by repeated subtraction
+ */
+ while (i >= 0)
+ {
+ if (timespec_ge(ts1, ts2))
+ {
+ ts1 = timespec_sub(ts1, ts2);
+ }
+
+ if (i == 0)
+ {
+ break;
+ }
+
+ i--;
+ if (ts2.tv_sec & 1)
+ {
+ ts2.tv_nsec += NSEC_PER_SEC;
+ }
+ ts2.tv_nsec >>= 1;
+ ts2.tv_sec >>= 1;
+ }
+
+ /* If signs differ and result is nonzero, subtract once more to cross zero
+ */
+ if (neg1 ^ neg2 && (ts1.tv_sec != 0 || ts1.tv_nsec != 0))
+ {
+ ts1 = timespec_sub(ts1, ts2);
+ }
+
+ /* Restore sign
+ */
+ if (neg1)
+ {
+ ts1.tv_sec = -ts1.tv_sec;
+ ts1.tv_nsec = -ts1.tv_nsec;
+ }
+
+ return ts1;
+}
+
+/** \fn struct timespec timespec_min(struct timespec ts1, struct timespec ts2)
+ * \brief Return the lesser one of the two given timespec values.
+*/
+struct timespec timespec_min(struct timespec ts1, struct timespec ts2) {
+ if(timespec_le(ts1, ts2)) {
+ return ts1;
+ } else {
+ return ts2;
+ }
+}
+
+/** \fn struct timespec timespec_max(struct timespec ts1, struct timespec ts2)
+ * \brief Return the greater one of the two given timespec values.
+*/
+struct timespec timespec_max(struct timespec ts1, struct timespec ts2) {
+ if(timespec_ge(ts1, ts2)) {
+ return ts1;
+ } else {
+ return ts2;
+ }
+}
+
+/** \fn struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max)
+ * \brief Clamp the value of TS between MIN and MAX.
+*/
+struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) {
+ if(timespec_gt(ts, max)) {
+ return max;
+ }
+ if(timespec_lt(ts, min)) {
+ return min;
+ }
+ return ts;
+}
+
+/** \fn int timespec_cmp(struct timespec ts1, struct timespec ts2)
+ * \brief Returns (1, 0, -1) if ts1 is (greater than, equal to, less than) to ts2.
+*/
+int timespec_cmp(struct timespec ts1, struct timespec ts2)
+{
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ if(ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec)
+ {
+ return 0;
+ }
+ else if((ts1.tv_sec > ts2.tv_sec)
+ || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec))
+ {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+}
+
+/** \fn bool timespec_eq(struct timespec ts1, struct timespec ts2)
+ * \brief Returns true if the two timespec structures are equal.
+*/
+bool timespec_eq(struct timespec ts1, struct timespec ts2)
+{
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ return (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec);
+}
+
+/** \fn bool timespec_gt(struct timespec ts1, struct timespec ts2)
+ * \brief Returns true if ts1 is greater than ts2.
+*/
+bool timespec_gt(struct timespec ts1, struct timespec ts2)
+{
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec));
+}
+
+/** \fn bool timespec_ge(struct timespec ts1, struct timespec ts2)
+ * \brief Returns true if ts1 is greater than or equal to ts2.
+*/
+bool timespec_ge(struct timespec ts1, struct timespec ts2)
+{
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec >= ts2.tv_nsec));
+}
+
+/** \fn bool timespec_lt(struct timespec ts1, struct timespec ts2)
+ * \brief Returns true if ts1 is less than ts2.
+*/
+bool timespec_lt(struct timespec ts1, struct timespec ts2)
+{
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec));
+}
+
+/** \fn bool timespec_le(struct timespec ts1, struct timespec ts2)
+ * \brief Returns true if ts1 is less than or equal to ts2.
+*/
+bool timespec_le(struct timespec ts1, struct timespec ts2)
+{
+ ts1 = timespec_normalise(ts1);
+ ts2 = timespec_normalise(ts2);
+
+ return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec <= ts2.tv_nsec));
+}
+
+/** \fn struct timespec timespec_from_double(double s)
+ * \brief Converts a fractional number of seconds to a timespec.
+*/
+struct timespec timespec_from_double(double s)
+{
+ struct timespec ts = {
+ .tv_sec = s,
+ .tv_nsec = (s - (long)(s)) * NSEC_PER_SEC,
+ };
+
+ return timespec_normalise(ts);
+}
+
+/** \fn double timespec_to_double(struct timespec ts)
+ * \brief Converts a timespec to a fractional number of seconds.
+*/
+double timespec_to_double(struct timespec ts)
+{
+ return ((double)(ts.tv_sec) + ((double)(ts.tv_nsec) / NSEC_PER_SEC));
+}
+
+/** \fn struct timespec timespec_from_timeval(struct timeval tv)
+ * \brief Converts a timeval to a timespec.
+*/
+struct timespec timespec_from_timeval(struct timeval tv)
+{
+ struct timespec ts = {
+ .tv_sec = tv.tv_sec,
+ .tv_nsec = tv.tv_usec * 1000
+ };
+
+ return timespec_normalise(ts);
+}
+
+/** \fn struct timeval timespec_to_timeval(struct timespec ts)
+ * \brief Converts a timespec to a timeval.
+*/
+struct timeval timespec_to_timeval(struct timespec ts)
+{
+ ts = timespec_normalise(ts);
+
+ struct timeval tv = {
+ .tv_sec = ts.tv_sec,
+ .tv_usec = ts.tv_nsec / 1000,
+ };
+
+ return tv;
+}
+
+/** \fn struct timespec timespec_from_ms(long milliseconds)
+ * \brief Converts an integer number of milliseconds to a timespec.
+*/
+struct timespec timespec_from_ms(long milliseconds)
+{
+ struct timespec ts = {
+ .tv_sec = (milliseconds / 1000),
+ .tv_nsec = (milliseconds % 1000) * 1000000,
+ };
+
+ return timespec_normalise(ts);
+}
+
+/** \fn long timespec_to_ms(struct timespec ts)
+ * \brief Converts a timespec to an integer number of milliseconds.
+*/
+long timespec_to_ms(struct timespec ts)
+{
+ return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
+}
+
+/** \fn struct timespec timespec_normalise(struct timespec ts)
+ * \brief Normalises a timespec structure.
+ *
+ * Returns a normalised version of a timespec structure, according to the
+ * following rules:
+ *
+ * 1) If tv_nsec is >=1,000,000,00 or <=-1,000,000,000, flatten the surplus
+ * nanoseconds into the tv_sec field.
+ *
+ * 2) If tv_nsec is negative, decrement tv_sec and roll tv_nsec up to represent
+ * the same value attainable by ADDING nanoseconds to tv_sec.
+*/
+struct timespec timespec_normalise(struct timespec ts)
+{
+ while(ts.tv_nsec >= NSEC_PER_SEC)
+ {
+ ++(ts.tv_sec);
+ ts.tv_nsec -= NSEC_PER_SEC;
+ }
+
+ while(ts.tv_nsec <= -NSEC_PER_SEC)
+ {
+ --(ts.tv_sec);
+ ts.tv_nsec += NSEC_PER_SEC;
+ }
+
+ if(ts.tv_nsec < 0)
+ {
+ /* Negative nanoseconds isn't valid according to POSIX.
+ * Decrement tv_sec and roll tv_nsec over.
+ */
+
+ --(ts.tv_sec);
+ ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec);
+ }
+
+ return ts;
+}
+
+#ifdef TEST
+#include <stdio.h>
+
+#define TEST_NORMALISE(ts_sec, ts_nsec, expect_sec, expect_nsec) { \
+ struct timespec in = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
+ struct timespec got = timespec_normalise(in); \
+ if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
+ { \
+ printf("%s:%d: timespec_normalise({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \
+ (long)(ts_sec), (long)(ts_nsec)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_BINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect_sec, expect_nsec) { \
+ struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \
+ struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \
+ struct timespec got = func(ts1, ts2); \
+ if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
+ { \
+ printf(#func "({%ld, %ld}, {%ld, %ld}) returned wrong values\n", \
+ (long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_TRINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, ts3_sec, ts3_nsec, expect_sec, expect_nsec) { \
+ struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \
+ struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \
+ struct timespec ts3 = { .tv_sec = ts3_sec, .tv_nsec = ts3_nsec }; \
+ struct timespec got = func(ts1, ts2, ts3); \
+ if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
+ { \
+ printf(#func "({%ld, %ld}, {%ld, %ld}, {%ld, %ld}) returned wrong values\n", \
+ (long)(ts1_sec), (long)(ts1_nsec), \
+ (long)(ts2_sec), (long)(ts2_nsec), \
+ (long)(ts3_sec), (long)(ts3_nsec)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_TEST_FUNC(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect) { \
+ struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \
+ struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \
+ int got = func(ts1, ts2); \
+ if(got != expect) \
+ { \
+ printf("%s:%d: " #func "({%ld, %ld}, {%ld, %ld}) returned %d, expected %s\n", __FILE__, __LINE__, \
+ (long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec), \
+ got, #expect); \
+ ++result; \
+ } \
+}
+
+#define TEST_FROM_DOUBLE(d_secs, expect_sec, expect_nsec) { \
+ struct timespec got = timespec_from_double(d_secs); \
+ if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
+ { \
+ printf("%s:%d: timespec_from_double(%f) returned wrong values\n", __FILE__, __LINE__, (double)(d_secs)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_TO_DOUBLE(ts_sec, ts_nsec, expect) { \
+ struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
+ double got = timespec_to_double(ts); \
+ if(got != expect) { \
+ printf("%s:%d: timespec_to_double({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \
+ (long)(ts_sec), (long)(ts_nsec)); \
+ printf(" Expected: %f\n", (double)(expect)); \
+ printf(" Got: %f\n", got); \
+ ++result; \
+ } \
+}
+
+#define TEST_FROM_TIMEVAL(in_sec, in_usec, expect_sec, expect_nsec) { \
+ struct timeval tv = { .tv_sec = in_sec, .tv_usec = in_usec }; \
+ struct timespec got = timespec_from_timeval(tv); \
+ if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
+ { \
+ printf("%s:%d: timespec_from_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \
+ (long)(in_sec), (long)(in_usec)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_TO_TIMEVAL(ts_sec, ts_nsec, expect_sec, expect_usec) { \
+ struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
+ struct timeval got = timespec_to_timeval(ts); \
+ if(got.tv_sec != expect_sec || got.tv_usec != expect_usec) \
+ { \
+ printf("%s:%d: timespec_to_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \
+ (long)(ts_sec), (long)(ts_nsec)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_usec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_usec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_FROM_MS(msecs, expect_sec, expect_nsec) { \
+ struct timespec got = timespec_from_ms(msecs); \
+ if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
+ { \
+ printf("%s:%d: timespec_from_ms(%ld) returned wrong values\n", __FILE__, __LINE__, (long)(msecs)); \
+ printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
+ printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
+ ++result; \
+ } \
+}
+
+#define TEST_TO_MS(ts_sec, ts_nsec, expect) { \
+ struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
+ long got = timespec_to_ms(ts); \
+ if(got != expect) { \
+ printf("%s:%d: timespec_to_ms({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \
+ (long)(ts_sec), (long)(ts_nsec)); \
+ printf(" Expected: %ld\n", (long)(expect)); \
+ printf(" Got: %ld\n", got); \
+ ++result; \
+ } \
+}
+
+int main()
+{
+ int result = 0;
+
+ // timespec_add
+
+ TEST_BINOP(timespec_add, 0,0, 0,0, 0,0);
+ TEST_BINOP(timespec_add, 0,0, 1,0, 1,0);
+ TEST_BINOP(timespec_add, 1,0, 0,0, 1,0);
+ TEST_BINOP(timespec_add, 1,0, 1,0, 2,0);
+ TEST_BINOP(timespec_add, 1,500000000, 1,0, 2,500000000);
+ TEST_BINOP(timespec_add, 1,0, 1,500000000, 2,500000000);
+ TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0);
+ TEST_BINOP(timespec_add, 1,500000000, 1,499999999, 2,999999999);
+ TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0);
+ TEST_BINOP(timespec_add, 1,999999999, 1,999999999, 3,999999998);
+ TEST_BINOP(timespec_add, 0,500000000, 1,500000000, 2,0);
+ TEST_BINOP(timespec_add, 1,500000000, 0,500000000, 2,0);
+
+ // timespec_sub
+
+ TEST_BINOP(timespec_sub, 0,0, 0,0, 0,0);
+ TEST_BINOP(timespec_sub, 1,0, 0,0, 1,0);
+ TEST_BINOP(timespec_sub, 1,0, 1,0, 0,0);
+ TEST_BINOP(timespec_sub, 1,500000000, 0,500000000, 1,0);
+ TEST_BINOP(timespec_sub, 5,500000000, 2,999999999, 2,500000001);
+ TEST_BINOP(timespec_sub, 0,0, 1,0, -1,0);
+ TEST_BINOP(timespec_sub, 0,500000000, 1,500000000, -1,0);
+ TEST_BINOP(timespec_sub, 0,0, 1,500000000, -2,500000000);
+ TEST_BINOP(timespec_sub, 1,0, 1,500000000, -1,500000000);
+ TEST_BINOP(timespec_sub, 1,0, 1,499999999, -1,500000001);
+
+ // timespec_mod
+
+ TEST_BINOP(timespec_mod, 0,0, 0,0, 0,0);
+ TEST_BINOP(timespec_mod, 0,0, 1,0, 0,0);
+ TEST_BINOP(timespec_mod, 1,0, 0,0, 1,0);
+ TEST_BINOP(timespec_mod, 1,0, 1,0, 0,0);
+ TEST_BINOP(timespec_mod, 10,0, 1,0, 0,0);
+ TEST_BINOP(timespec_mod, 10,0, 3,0, 1,0);
+ TEST_BINOP(timespec_mod, 10,0, -3,0, -2,0);
+ TEST_BINOP(timespec_mod, -10,0, 3,0, 2,0);
+ TEST_BINOP(timespec_mod, -10,0, -3,0, -1,0);
+ TEST_BINOP(timespec_mod, 10,0, 5,0, 0,0);
+ TEST_BINOP(timespec_mod, 10,0, -5,0, 0,0);
+ TEST_BINOP(timespec_mod, -10,0, 5,0, 0,0);
+ TEST_BINOP(timespec_mod, -10,0, -5,0, 0,0);
+ TEST_BINOP(timespec_mod, 1,500000000, 0,500000000, 0,0);
+ TEST_BINOP(timespec_mod, 5,500000000, 2,999999999, 2,500000001);
+ TEST_BINOP(timespec_mod, 0,500000000, 1,500000000, 0,500000000);
+ TEST_BINOP(timespec_mod, 0,0, 1,500000000, 0,0);
+ TEST_BINOP(timespec_mod, 1,0, 1,500000000, 1,0);
+ TEST_BINOP(timespec_mod, 1,0, 0,1, 0,0);
+ TEST_BINOP(timespec_mod, 1,123456789, 0,1000, 0,789);
+ TEST_BINOP(timespec_mod, 1,0, 0,9999999, 0,100);
+ TEST_BINOP(timespec_mod, 12345,54321, 0,100001, 0,5555);
+ TEST_BINOP(timespec_mod, LONG_MAX,0, 0,1, 0,0);
+ TEST_BINOP(timespec_mod, LONG_MAX,0, LONG_MAX,1, LONG_MAX,0);
+
+ // timespec_clamp
+
+ TEST_TRINOP(timespec_clamp, 0,0, 0,0, 0,0, 0,0);
+
+ TEST_TRINOP(timespec_clamp, 1000,0, 2000,0, 3000,0, 2000,0);
+ TEST_TRINOP(timespec_clamp, 1500,0, 2000,0, 3000,0, 2000,0);
+ TEST_TRINOP(timespec_clamp, 1999,0, 2000,0, 3000,0, 2000,0);
+ TEST_TRINOP(timespec_clamp, 2000,0, 2000,0, 3000,0, 2000,0);
+ TEST_TRINOP(timespec_clamp, 2001,0, 2000,0, 3000,0, 2001,0);
+ TEST_TRINOP(timespec_clamp, 2250,0, 2000,0, 3000,0, 2250,0);
+ TEST_TRINOP(timespec_clamp, 2500,0, 2000,0, 3000,0, 2500,0);
+ TEST_TRINOP(timespec_clamp, 2750,0, 2000,0, 3000,0, 2750,0);
+ TEST_TRINOP(timespec_clamp, 2999,0, 2000,0, 3000,0, 2999,0);
+ TEST_TRINOP(timespec_clamp, 3000,0, 2000,0, 3000,0, 3000,0);
+ TEST_TRINOP(timespec_clamp, 3001,0, 2000,0, 3000,0, 3000,0);
+ TEST_TRINOP(timespec_clamp, 3500,0, 2000,0, 3000,0, 3000,0);
+ TEST_TRINOP(timespec_clamp, 4000,0, 2000,0, 3000,0, 3000,0);
+
+ TEST_TRINOP(timespec_clamp, 0,1000, 0,2000, 0,3000, 0,2000);
+ TEST_TRINOP(timespec_clamp, 0,1500, 0,2000, 0,3000, 0,2000);
+ TEST_TRINOP(timespec_clamp, 0,1999, 0,2000, 0,3000, 0,2000);
+ TEST_TRINOP(timespec_clamp, 0,2000, 0,2000, 0,3000, 0,2000);
+ TEST_TRINOP(timespec_clamp, 0,2001, 0,2000, 0,3000, 0,2001);
+ TEST_TRINOP(timespec_clamp, 0,2250, 0,2000, 0,3000, 0,2250);
+ TEST_TRINOP(timespec_clamp, 0,2500, 0,2000, 0,3000, 0,2500);
+ TEST_TRINOP(timespec_clamp, 0,2750, 0,2000, 0,3000, 0,2750);
+ TEST_TRINOP(timespec_clamp, 0,2999, 0,2000, 0,3000, 0,2999);
+ TEST_TRINOP(timespec_clamp, 0,3000, 0,2000, 0,3000, 0,3000);
+ TEST_TRINOP(timespec_clamp, 0,3001, 0,2000, 0,3000, 0,3000);
+ TEST_TRINOP(timespec_clamp, 0,3500, 0,2000, 0,3000, 0,3000);
+ TEST_TRINOP(timespec_clamp, 0,4000, 0,2000, 0,3000, 0,3000);
+
+ TEST_TRINOP(timespec_clamp,0,-1000, 0,-3000, 0,-2000, 0,-2000);
+ TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,-2000, 0,-2000);
+ TEST_TRINOP(timespec_clamp,0,-1999, 0,-3000, 0,-2000, 0,-2000);
+ TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,-2000, 0,-3000);
+ TEST_TRINOP(timespec_clamp,0,-2001, 0,-3000, 0,-2000, 0,-2001);
+ TEST_TRINOP(timespec_clamp,0,-2250, 0,-3000, 0,-2000, 0,-2250);
+ TEST_TRINOP(timespec_clamp,0,-2500, 0,-3000, 0,-2000, 0,-2500);
+ TEST_TRINOP(timespec_clamp,0,-2750, 0,-3000, 0,-2000, 0,-2750);
+ TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,-2000, 0,-2999);
+ TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000);
+ TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,-2000, 0,-3000);
+ TEST_TRINOP(timespec_clamp,0,-3500, 0,-3000, 0,-2000, 0,-3000);
+ TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000);
+
+ TEST_TRINOP(timespec_clamp,0,-4000, 0,-3000, 0,3000, 0,-3000);
+ TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,3000, 0,-3000);
+ TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,3000, 0,-3000);
+ TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,3000, 0,-2999);
+ TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,3000, 0,-1500);
+ TEST_TRINOP(timespec_clamp,0, -1, 0,-3000, 0,3000, 0, -1);
+ TEST_TRINOP(timespec_clamp,0, 0, 0,-3000, 0,3000, 0, 0);
+ TEST_TRINOP(timespec_clamp,0, 1, 0,-3000, 0,3000, 0, 1);
+ TEST_TRINOP(timespec_clamp,0, 1500, 0,-3000, 0,3000, 0, 1500);
+ TEST_TRINOP(timespec_clamp,0, 2999, 0,-3000, 0,3000, 0, 2999);
+ TEST_TRINOP(timespec_clamp,0, 3000, 0,-3000, 0,3000, 0, 3000);
+ TEST_TRINOP(timespec_clamp,0, 3001, 0,-3000, 0,3000, 0, 3000);
+ TEST_TRINOP(timespec_clamp,0, 4000, 0,-3000, 0,3000, 0, 3000);
+
+ // timespec_min
+
+ TEST_BINOP(timespec_min, 0,0, 0,0, 0,0);
+ TEST_BINOP(timespec_min, 0,0, 1,0, 0,0);
+ TEST_BINOP(timespec_min, 1,0, 0,0, 0,0);
+ TEST_BINOP(timespec_min, 1,0, 1,0, 1,0);
+ TEST_BINOP(timespec_min, 10,0, 1,0, 1,0);
+ TEST_BINOP(timespec_min, 10,0, 3,0, 3,0);
+ TEST_BINOP(timespec_min, 10,0, -3,0, -3,0);
+ TEST_BINOP(timespec_min, -10,0, 3,0, -10,0);
+ TEST_BINOP(timespec_min, -10,0, -3,0, -10,0);
+ TEST_BINOP(timespec_min, 10,0, 5,0, 5,0);
+ TEST_BINOP(timespec_min, 10,0, -5,0, -5,0);
+ TEST_BINOP(timespec_min, -10,0, 5,0, -10,0);
+ TEST_BINOP(timespec_min, -10,0, -5,0, -10,0);
+ TEST_BINOP(timespec_min, 1,500000000, 0,500000000, 0,500000000);
+ TEST_BINOP(timespec_min, 5,500000000, 2,999999999, 2,999999999);
+ TEST_BINOP(timespec_min, 0,500000000, 1,500000000, 0,500000000);
+ TEST_BINOP(timespec_min, 0,0, 1,500000000, 0,0);
+ TEST_BINOP(timespec_min, 1,0, 1,500000000, 1,0);
+ TEST_BINOP(timespec_min, 1,0, 0,1, 0,1);
+ TEST_BINOP(timespec_min, 1,123456789, 0,1000, 0,1000);
+ TEST_BINOP(timespec_min, 1,0, 0,9999999, 0,9999999);
+ TEST_BINOP(timespec_min, 12345,54321, 0,100001, 0,100001);
+ TEST_BINOP(timespec_min, LONG_MIN,0, 0,1, LONG_MIN,0);
+ TEST_BINOP(timespec_min, LONG_MIN,0, 0,-1, LONG_MIN,0);
+ TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MAX,0, LONG_MIN,0);
+ TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0);
+ TEST_BINOP(timespec_min, LONG_MAX,0, 0,1, 0,1);
+ TEST_BINOP(timespec_min, LONG_MAX,0, 0,-1, 0,-1);
+ TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0);
+ TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MIN,0, LONG_MIN,0);
+
+ // timespec_max
+
+ TEST_BINOP(timespec_max, 0,0, 0,0, 0,0);
+ TEST_BINOP(timespec_max, 0,0, 1,0, 1,0);
+ TEST_BINOP(timespec_max, 1,0, 0,0, 1,0);
+ TEST_BINOP(timespec_max, 1,0, 1,0, 1,0);
+ TEST_BINOP(timespec_max, 10,0, 1,0, 10,0);
+ TEST_BINOP(timespec_max, 10,0, 3,0, 10,0);
+ TEST_BINOP(timespec_max, 10,0, -3,0, 10,0);
+ TEST_BINOP(timespec_max, -10,0, 3,0, 3,0);
+ TEST_BINOP(timespec_max, -10,0, -3,0, -3,0);
+ TEST_BINOP(timespec_max, 10,0, 5,0, 10,0);
+ TEST_BINOP(timespec_max, 10,0, -5,0, 10,0);
+ TEST_BINOP(timespec_max, -10,0, 5,0, 5,0);
+ TEST_BINOP(timespec_max, -10,0, -5,0, -5,0);
+ TEST_BINOP(timespec_max, 1,500000000, 0,500000000, 1,500000000);
+ TEST_BINOP(timespec_max, 5,500000000, 2,999999999, 5,500000000);
+ TEST_BINOP(timespec_max, 0,500000000, 1,500000000, 1,500000000);
+ TEST_BINOP(timespec_max, 0,0, 1,500000000, 1,500000000);
+ TEST_BINOP(timespec_max, 1,0, 1,500000000, 1,500000000);
+ TEST_BINOP(timespec_max, 1,0, 0,1, 1,0);
+ TEST_BINOP(timespec_max, 1,123456789, 0,1000, 1,123456789);
+ TEST_BINOP(timespec_max, 1,0, 0,9999999, 1,0);
+ TEST_BINOP(timespec_max, 12345,54321, 0,100001, 12345,54321);
+ TEST_BINOP(timespec_max, LONG_MIN,0, 0,1, 0,1);
+ TEST_BINOP(timespec_max, LONG_MIN,0, 0,-1, 0,-1);
+ TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MAX,0, LONG_MAX,0);
+ TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0);
+ TEST_BINOP(timespec_max, LONG_MAX,0, 0,1, LONG_MAX,0);
+ TEST_BINOP(timespec_max, LONG_MAX,0, 0,-1, LONG_MAX,0);
+ TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0);
+ TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MIN,0, LONG_MAX,0);
+
+ // timespec_cmp
+
+ TEST_TEST_FUNC(timespec_cmp, 0,0, 0,0, 0);
+ TEST_TEST_FUNC(timespec_cmp, 100,0, 100,0, 0);
+ TEST_TEST_FUNC(timespec_cmp, -100,0, -100,0, 0);
+
+ TEST_TEST_FUNC(timespec_cmp, 1,0, 0,0, 1);
+ TEST_TEST_FUNC(timespec_cmp, 0,0, 1,0, -1);
+ TEST_TEST_FUNC(timespec_cmp, 0,1, 0,0, 1);
+ TEST_TEST_FUNC(timespec_cmp, 0,0, 0,1, -1);
+ TEST_TEST_FUNC(timespec_cmp, 1,0, 0,100, 1);
+ TEST_TEST_FUNC(timespec_cmp, 0,100 , 1,0, -1);
+
+ TEST_TEST_FUNC(timespec_cmp, -0,-0, 0,0, 0);
+ TEST_TEST_FUNC(timespec_cmp, -10,-500000000, -11,500000000, 0);
+ TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,499999999, 0);
+ TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,500000001, -1);
+ TEST_TEST_FUNC(timespec_cmp, -11,500000001, -10,-500000001, 1);
+
+ // timespec_eq
+
+ TEST_TEST_FUNC(timespec_eq, 0,0, 0,0, true);
+ TEST_TEST_FUNC(timespec_eq, 100,0, 100,0, true);
+ TEST_TEST_FUNC(timespec_eq, -200,0, -200,0, true);
+ TEST_TEST_FUNC(timespec_eq, 0,300, 0,300, true);
+ TEST_TEST_FUNC(timespec_eq, 0,-400, 0,-400, true);
+
+ TEST_TEST_FUNC(timespec_eq, 100,1, 100,0, false);
+ TEST_TEST_FUNC(timespec_eq, 101,0, 100,0, false);
+ TEST_TEST_FUNC(timespec_eq, -100,0, 100,0, false);
+ TEST_TEST_FUNC(timespec_eq, 0,10, 0,-10, false);
+
+ TEST_TEST_FUNC(timespec_eq, -0,-0, 0,0, true);
+ TEST_TEST_FUNC(timespec_eq, -10,-500000000, -11,500000000, true);
+ TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,499999999, true);
+ TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,500000001, false);
+
+ // timespec_gt
+
+ TEST_TEST_FUNC(timespec_gt, 1,0, 0,0, true);
+ TEST_TEST_FUNC(timespec_gt, 0,0, -1,0, true);
+ TEST_TEST_FUNC(timespec_gt, 0,1, 0,0, true);
+ TEST_TEST_FUNC(timespec_gt, 0,0, 0,-1, true);
+
+ TEST_TEST_FUNC(timespec_gt, 1,0, 1,0, false);
+ TEST_TEST_FUNC(timespec_gt, 1,1, 1,1, false);
+ TEST_TEST_FUNC(timespec_gt, -1,0, 0,0, false);
+ TEST_TEST_FUNC(timespec_gt, 0,-1, 0,0, false);
+
+ TEST_TEST_FUNC(timespec_gt, 0,0, -0,-0, false);
+ TEST_TEST_FUNC(timespec_gt, -10,-500000000, -11,500000000, false);
+ TEST_TEST_FUNC(timespec_gt, -11,500000000, -10,-500000000, false);
+ TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,499999999, false);
+ TEST_TEST_FUNC(timespec_gt, -11,499999999, -11,499999999, false);
+ TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,500000001, false);
+ TEST_TEST_FUNC(timespec_gt, -11,500000001, -10,-500000001, true);
+
+ // timespec_ge
+
+ TEST_TEST_FUNC(timespec_ge, 1,0, 0,0, true);
+ TEST_TEST_FUNC(timespec_ge, 0,0, -1,0, true);
+ TEST_TEST_FUNC(timespec_ge, 0,1, 0,0, true);
+ TEST_TEST_FUNC(timespec_ge, 0,0, 0,-1, true);
+ TEST_TEST_FUNC(timespec_ge, 1,0, 1,0, true);
+ TEST_TEST_FUNC(timespec_ge, 1,1, 1,1, true);
+
+ TEST_TEST_FUNC(timespec_ge, -1,0, 0,0, false);
+ TEST_TEST_FUNC(timespec_ge, 0,-1, 0,0, false);
+
+ TEST_TEST_FUNC(timespec_ge, 0,0, -0,-0, true);
+ TEST_TEST_FUNC(timespec_ge, -10,-500000000, -11,500000000, true);
+ TEST_TEST_FUNC(timespec_ge, -11,500000000, -10,-500000000, true);
+ TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,499999999, true);
+ TEST_TEST_FUNC(timespec_ge, -11,499999999, -11,499999999, true);
+ TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,500000001, false);
+ TEST_TEST_FUNC(timespec_ge, -11,500000001, -10,-500000001, true);
+
+ // timespec_lt
+
+ TEST_TEST_FUNC(timespec_lt, 0,0, 1,0, true);
+ TEST_TEST_FUNC(timespec_lt, -1,0, 0,0, true);
+ TEST_TEST_FUNC(timespec_lt, 0,0, 0,1, true);
+ TEST_TEST_FUNC(timespec_lt, 0,-1, 0,0, true);
+
+ TEST_TEST_FUNC(timespec_lt, 1,0, 1,0, false);
+ TEST_TEST_FUNC(timespec_lt, 1,1, 1,1, false);
+ TEST_TEST_FUNC(timespec_lt, 0,0, -1,0, false);
+ TEST_TEST_FUNC(timespec_lt, 0,0, 0,-1, false);
+
+ TEST_TEST_FUNC(timespec_lt, 0,0, -0,-0, false);
+ TEST_TEST_FUNC(timespec_lt, -10,-500000000, -11,500000000, false);
+ TEST_TEST_FUNC(timespec_lt, -11,500000000, -10,-500000000, false);
+ TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,499999999, false);
+ TEST_TEST_FUNC(timespec_lt, -11,499999999, -11,499999999, false);
+ TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,500000001, true);
+ TEST_TEST_FUNC(timespec_lt, -11,500000001, -10,-500000001, false);
+
+ // timespec_le
+
+ TEST_TEST_FUNC(timespec_le, 0,0, 1,0, true);
+ TEST_TEST_FUNC(timespec_le, -1,0, 0,0, true);
+ TEST_TEST_FUNC(timespec_le, 0,0, 0,1, true);
+ TEST_TEST_FUNC(timespec_le, 0,-1, 0,0, true);
+ TEST_TEST_FUNC(timespec_le, 1,0, 1,0, true);
+ TEST_TEST_FUNC(timespec_le, 1,1, 1,1, true);
+
+ TEST_TEST_FUNC(timespec_le, 0,0, -1,0, false);
+ TEST_TEST_FUNC(timespec_le, 0,0, 0,-1, false);
+
+ TEST_TEST_FUNC(timespec_le, 0,0, -0,-0, true);
+ TEST_TEST_FUNC(timespec_le, -10,-500000000, -11,500000000, true);
+ TEST_TEST_FUNC(timespec_le, -11,500000000, -10,-500000000, true);
+ TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,499999999, true);
+ TEST_TEST_FUNC(timespec_le, -11,499999999, -11,499999999, true);
+ TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,500000001, true);
+ TEST_TEST_FUNC(timespec_le, -11,500000001, -10,-500000001, false);
+
+ // timespec_from_double
+
+ TEST_FROM_DOUBLE(0.0, 0,0);
+ TEST_FROM_DOUBLE(10.0, 10,0);
+ TEST_FROM_DOUBLE(-10.0, -10,0);
+ TEST_FROM_DOUBLE(0.5, 0,500000000);
+ TEST_FROM_DOUBLE(-0.5, -1,500000000);
+ TEST_FROM_DOUBLE(10.5, 10,500000000);
+ TEST_FROM_DOUBLE(-10.5, -11,500000000);
+
+ // timespec_to_double
+
+ TEST_TO_DOUBLE(0,0, 0.0);
+ TEST_TO_DOUBLE(10,0, 10.0);
+ TEST_TO_DOUBLE(-10,0, -10.0);
+ TEST_TO_DOUBLE(0,500000000, 0.5);
+ TEST_TO_DOUBLE(0,-500000000, -0.5);
+ TEST_TO_DOUBLE(10,500000000, 10.5);
+ TEST_TO_DOUBLE(10,-500000000, 9.5);
+ TEST_TO_DOUBLE(-10,500000000, -9.5);
+ TEST_TO_DOUBLE(-10,-500000000, -10.5);
+
+ // timespec_from_timeval
+
+ TEST_FROM_TIMEVAL(0,0, 0,0);
+ TEST_FROM_TIMEVAL(1,0, 1,0);
+ TEST_FROM_TIMEVAL(1000,0, 1000,0);
+ TEST_FROM_TIMEVAL(0,0, 0,0);
+ TEST_FROM_TIMEVAL(-1,0, -1,0);
+ TEST_FROM_TIMEVAL(-1000,0, -1000,0);
+
+ TEST_FROM_TIMEVAL(1,1, 1,1000);
+ TEST_FROM_TIMEVAL(1,1000, 1,1000000);
+ TEST_FROM_TIMEVAL(1,-1, 0,999999000);
+ TEST_FROM_TIMEVAL(1,-1000, 0,999000000);
+ TEST_FROM_TIMEVAL(-1,-1, -2,999999000);
+ TEST_FROM_TIMEVAL(-1,-1000, -2,999000000);
+
+ // timespec_to_timeval
+
+ TEST_TO_TIMEVAL(0,0, 0,0);
+ TEST_TO_TIMEVAL(1,0, 1,0);
+ TEST_TO_TIMEVAL(10,0, 10,0);
+ TEST_TO_TIMEVAL(-1,0, -1,0);
+ TEST_TO_TIMEVAL(-10,0, -10,0);
+
+ TEST_TO_TIMEVAL(1,1, 1,0);
+ TEST_TO_TIMEVAL(1,999, 1,0);
+ TEST_TO_TIMEVAL(1,1000, 1,1);
+ TEST_TO_TIMEVAL(1,1001, 1,1);
+ TEST_TO_TIMEVAL(1,2000, 1,2);
+ TEST_TO_TIMEVAL(1,2000000, 1,2000);
+
+ TEST_TO_TIMEVAL(1,-1, 0,999999);
+ TEST_TO_TIMEVAL(1,-999, 0,999999);
+ TEST_TO_TIMEVAL(1,-1000, 0,999999);
+ TEST_TO_TIMEVAL(1,-1001, 0,999998);
+ TEST_TO_TIMEVAL(1,-2000, 0,999998);
+ TEST_TO_TIMEVAL(1,-2000000, 0,998000);
+
+ TEST_TO_TIMEVAL(-1,-1, -2,999999);
+ TEST_TO_TIMEVAL(-1,-999, -2,999999);
+ TEST_TO_TIMEVAL(-1,-1000, -2,999999);
+ TEST_TO_TIMEVAL(-1,-1001, -2,999998);
+ TEST_TO_TIMEVAL(-1,-2000, -2,999998);
+ TEST_TO_TIMEVAL(-1,-2000000, -2,998000);
+
+ TEST_TO_TIMEVAL(1,1500000000, 2,500000);
+ TEST_TO_TIMEVAL(1,-1500000000, -1,500000);
+ TEST_TO_TIMEVAL(-1,-1500000000, -3,500000);
+
+ // timespec_from_ms
+
+ TEST_FROM_MS(0, 0,0);
+ TEST_FROM_MS(1, 0,1000000);
+ TEST_FROM_MS(-1, -1,999000000);
+ TEST_FROM_MS(1500, 1,500000000);
+ TEST_FROM_MS(-1000, -1,0);
+ TEST_FROM_MS(-1500, -2,500000000);
+
+ // timespec_to_ms
+
+ TEST_TO_MS(0,0, 0);
+ TEST_TO_MS(10,0, 10000);
+ TEST_TO_MS(-10,0, -10000);
+ TEST_TO_MS(0,500000000, 500);
+ TEST_TO_MS(0,-500000000, -500);
+ TEST_TO_MS(10,500000000, 10500);
+ TEST_TO_MS(10,-500000000, 9500);
+ TEST_TO_MS(-10,500000000, -9500);
+ TEST_TO_MS(-10,-500000000, -10500);
+
+ // timespec_normalise
+
+ TEST_NORMALISE(0,0, 0,0);
+
+ TEST_NORMALISE(0,1000000000, 1,0);
+ TEST_NORMALISE(0,1500000000, 1,500000000);
+ TEST_NORMALISE(0,-1000000000, -1,0);
+ TEST_NORMALISE(0,-1500000000, -2,500000000);
+
+ TEST_NORMALISE(5,1000000000, 6,0);
+ TEST_NORMALISE(5,1500000000, 6,500000000);
+ TEST_NORMALISE(-5,-1000000000, -6,0);
+ TEST_NORMALISE(-5,-1500000000, -7,500000000);
+
+ TEST_NORMALISE(0,2000000000, 2,0);
+ TEST_NORMALISE(0,2100000000, 2,100000000);
+ TEST_NORMALISE(0,-2000000000, -2,0);
+ TEST_NORMALISE(0,-2100000000, -3,900000000);
+
+ TEST_NORMALISE(1,-500000001, 0,499999999);
+ TEST_NORMALISE(1,-500000000, 0,500000000);
+ TEST_NORMALISE(1,-499999999, 0,500000001);
+ TEST_NORMALISE(0,-499999999, -1,500000001);
+
+ TEST_NORMALISE(-1,500000000, -1,500000000);
+ TEST_NORMALISE(-1,499999999, -1,499999999);
+
+ if(result > 0)
+ {
+ printf("%d tests failed\n", result);
+ }
+ else{
+ printf("All tests passed\n");
+ }
+
+ return !!result; /* Don't overflow the exit status */
+}
+#endif