aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--support/Makefile1
-rw-r--r--support/timespec.c64
-rw-r--r--support/timespec.h8
-rw-r--r--support/tst-timespec.c320
-rw-r--r--time/tst-cpuclock1.c52
5 files changed, 415 insertions, 30 deletions
diff --git a/support/Makefile b/support/Makefile
index 764b471..e74e0dd 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -238,6 +238,7 @@ tests = \
tst-test_compare \
tst-test_compare_blob \
tst-test_compare_string \
+ tst-timespec \
tst-xreadlink \
tst-xsigstack \
diff --git a/support/timespec.c b/support/timespec.c
index ea6b947..9f5449e 100644
--- a/support/timespec.c
+++ b/support/timespec.c
@@ -19,6 +19,8 @@
#include <support/timespec.h>
#include <stdio.h>
#include <stdint.h>
+#include <assert.h>
+#include <intprops.h>
void
test_timespec_before_impl (const char *file, int line,
@@ -57,3 +59,65 @@ test_timespec_equal_or_after_impl (const char *file, int line,
(intmax_t) diff.tv_sec, (intmax_t) diff.tv_nsec);
}
}
+
+/* Convert TIME to nanoseconds stored in a long.
+ Returns long maximum or minimum if the conversion overflows
+ or underflows, respectively. */
+long
+support_timespec_ns (struct timespec time)
+{
+ long time_ns;
+ if (INT_MULTIPLY_WRAPV(time.tv_sec, TIMESPEC_HZ, &time_ns))
+ {
+ return (time.tv_sec < 0) ? TYPE_MINIMUM(long): TYPE_MAXIMUM(long);
+ }
+ if (INT_ADD_WRAPV(time_ns, time.tv_nsec, &time_ns))
+ {
+ return (time.tv_nsec < 0) ? TYPE_MINIMUM(long): TYPE_MAXIMUM(long);
+ }
+ return time_ns;
+}
+
+/* Returns time normalized timespec with .tv_nsec < TIMESPEC_HZ
+ and the whole seconds added to .tv_sec. If an overflow or
+ underflow occurs the values are clamped to its maximum or
+ minimum respectively. */
+struct timespec
+support_timespec_normalize (struct timespec time)
+{
+ struct timespec norm;
+ if (INT_ADD_WRAPV (time.tv_sec, (time.tv_nsec / TIMESPEC_HZ), &norm.tv_sec))
+ {
+ norm.tv_sec = (time.tv_nsec < 0) ? TYPE_MINIMUM (time_t): TYPE_MAXIMUM (time_t);
+ norm.tv_nsec = (time.tv_nsec < 0) ? -1 * (TIMESPEC_HZ - 1) : TIMESPEC_HZ - 1;
+ return norm;
+ }
+ norm.tv_nsec = time.tv_nsec % TIMESPEC_HZ;
+ return norm;
+}
+
+/* Returns TRUE if the observed time is within the given percentage
+ bounds of the expected time, and FALSE otherwise.
+ For example the call
+
+ support_timespec_check_in_range(expected, observed, 0.5, 1.2);
+
+ will check if
+
+ 0.5 of expected <= observed <= 1.2 of expected
+
+ In other words it will check if observed time is within 50% to
+ 120% of the expected time. */
+int
+support_timespec_check_in_range (struct timespec expected, struct timespec observed,
+ double lower_bound, double upper_bound)
+{
+ assert (upper_bound >= lower_bound);
+ long expected_norm, observed_norm;
+ expected_norm = support_timespec_ns (expected);
+ /* Don't divide by zero */
+ assert(expected_norm != 0);
+ observed_norm = support_timespec_ns (observed);
+ double ratio = (double)observed_norm / expected_norm;
+ return (lower_bound <= ratio && ratio <= upper_bound);
+}
diff --git a/support/timespec.h b/support/timespec.h
index c5852df..fd54667 100644
--- a/support/timespec.h
+++ b/support/timespec.h
@@ -48,6 +48,14 @@ void test_timespec_equal_or_after_impl (const char *file, int line,
const struct timespec left,
const struct timespec right);
+long support_timespec_ns (struct timespec time);
+
+struct timespec support_timespec_normalize (struct timespec time);
+
+int support_timespec_check_in_range (struct timespec expected, struct timespec observed,
+ double lower_bound, double upper_bound);
+
+
/* Check that the timespec on the left represents a time before the
time on the right. */
#define TEST_TIMESPEC_BEFORE(left, right) \
diff --git a/support/tst-timespec.c b/support/tst-timespec.c
new file mode 100644
index 0000000..7142355
--- /dev/null
+++ b/support/tst-timespec.c
@@ -0,0 +1,320 @@
+/* Test for support_timespec_check_in_range function.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/timespec.h>
+#include <support/check.h>
+#include <limits.h>
+
+#define TIMESPEC_HZ 1000000000
+
+struct timespec_ns_test_case
+{
+ struct timespec time;
+ long time_ns;
+};
+
+struct timespec_norm_test_case
+{
+ struct timespec time;
+ struct timespec norm;
+};
+
+struct timespec_test_case
+{
+ struct timespec expected;
+ struct timespec observed;
+ double upper_bound;
+ double lower_bound;
+ int result;
+};
+
+/* Test cases for timespec_ns */
+struct timespec_ns_test_case ns_cases[] = {
+ {.time = {.tv_sec = 0, .tv_nsec = 0},
+ .time_ns = 0,
+ },
+ {.time = {.tv_sec = 0, .tv_nsec = 1},
+ .time_ns = 1,
+ },
+ {.time = {.tv_sec = 1, .tv_nsec = 0},
+ .time_ns = TIMESPEC_HZ,
+ },
+ {.time = {.tv_sec = 1, .tv_nsec = 1},
+ .time_ns = TIMESPEC_HZ + 1,
+ },
+ {.time = {.tv_sec = 0, .tv_nsec = -1},
+ .time_ns = -1,
+ },
+ {.time = {.tv_sec = -1, .tv_nsec = 0},
+ .time_ns = -TIMESPEC_HZ,
+ },
+ {.time = {.tv_sec = -1, .tv_nsec = -1},
+ .time_ns = -TIMESPEC_HZ - 1,
+ },
+ {.time = {.tv_sec = 1, .tv_nsec = -1},
+ .time_ns = TIMESPEC_HZ - 1,
+ },
+ {.time = {.tv_sec = -1, .tv_nsec = 1},
+ .time_ns = -TIMESPEC_HZ + 1,
+ },
+ /* Overflow bondary by 2 */
+ {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ, .tv_nsec = LONG_MAX%TIMESPEC_HZ - 1},
+ .time_ns = LONG_MAX - 1,
+ },
+ /* Overflow bondary */
+ {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ, .tv_nsec = LONG_MAX%TIMESPEC_HZ},
+ .time_ns = LONG_MAX,
+ },
+ /* Underflow bondary by 1 */
+ {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ, .tv_nsec = LONG_MIN%TIMESPEC_HZ + 1},
+ .time_ns = LONG_MIN + 1,
+ },
+ /* Underflow bondary */
+ {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ, .tv_nsec = LONG_MIN%TIMESPEC_HZ},
+ .time_ns = LONG_MIN,
+ },
+ /* Multiplication overflow */
+ {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ + 1, .tv_nsec = 1},
+ .time_ns = LONG_MAX,
+ },
+ /* Multiplication underflow */
+ {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ - 1, .tv_nsec = -1},
+ .time_ns = LONG_MIN,
+ },
+ /* Sum overflows */
+ {.time = {.tv_sec = LONG_MAX / TIMESPEC_HZ, .tv_nsec = LONG_MAX%TIMESPEC_HZ + 1},
+ .time_ns = LONG_MAX,
+ },
+ /* Sum underflow */
+ {.time = {.tv_sec = LONG_MIN / TIMESPEC_HZ, .tv_nsec = LONG_MIN%TIMESPEC_HZ - 1},
+ .time_ns = LONG_MIN,
+ }
+};
+
+/* Test cases for timespec_norm */
+struct timespec_norm_test_case norm_cases[] = {
+ /* Positive cases */
+ {.time = {.tv_sec = 0, .tv_nsec = 0},
+ .norm = {.tv_sec = 0, .tv_nsec = 0}
+ },
+ {.time = {.tv_sec = 1, .tv_nsec = 0},
+ .norm = {.tv_sec = 1, .tv_nsec = 0}
+ },
+ {.time = {.tv_sec = 0, .tv_nsec = 1},
+ .norm = {.tv_sec = 0, .tv_nsec = 1}
+ },
+ {.time = {.tv_sec = 0, .tv_nsec = TIMESPEC_HZ},
+ .norm = {.tv_sec = 1, .tv_nsec = 0}
+ },
+ {.time = {.tv_sec = 0, .tv_nsec = TIMESPEC_HZ + 1},
+ .norm = {.tv_sec = 1, .tv_nsec = 1}
+ },
+ {.time = {.tv_sec = 1, .tv_nsec = TIMESPEC_HZ},
+ .norm = {.tv_sec = 2, .tv_nsec = 0}
+ },
+ {.time = {.tv_sec = 1, .tv_nsec = TIMESPEC_HZ + 1},
+ .norm = {.tv_sec = 2, .tv_nsec = 1}
+ },
+ /* Negative cases */
+ {.time = {.tv_sec = 0, .tv_nsec = -TIMESPEC_HZ},
+ .norm = {.tv_sec = -1, .tv_nsec = 0}
+ },
+ {.time = {.tv_sec = 0, .tv_nsec = -TIMESPEC_HZ - 1},
+ .norm = {.tv_sec = -1, .tv_nsec = -1}
+ },
+ {.time = {.tv_sec = -1, .tv_nsec = -TIMESPEC_HZ},
+ .norm = {.tv_sec = -2, .tv_nsec = 0}
+ },
+ {.time = {.tv_sec = -1, .tv_nsec = -TIMESPEC_HZ - 1},
+ .norm = {.tv_sec = -2, .tv_nsec = -1}
+ },
+ /* Overflow bondary by 2 */
+ {.time = {.tv_sec = LONG_MAX - 2, .tv_nsec = TIMESPEC_HZ + 1},
+ .norm = {.tv_sec = LONG_MAX - 1, 1},
+ },
+ /* Overflow bondary by 1 */
+ {.time = {.tv_sec = LONG_MAX - 1, .tv_nsec = TIMESPEC_HZ + 1},
+ .norm = {.tv_sec = LONG_MAX, .tv_nsec = 1},
+ },
+ /* Underflow bondary by 2 */
+ {.time = {.tv_sec = LONG_MIN + 2, .tv_nsec = -TIMESPEC_HZ - 1},
+ .norm = {.tv_sec = LONG_MIN + 1, -1},
+ },
+ /* Underflow bondary by 1 */
+ {.time = {.tv_sec = LONG_MIN + 1, .tv_nsec = -TIMESPEC_HZ - 1},
+ .norm = {.tv_sec = LONG_MIN, .tv_nsec = -1},
+ },
+ /* SUM overflow */
+ {.time = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ},
+ .norm = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ - 1},
+ },
+ /* SUM underflow */
+ {.time = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ},
+ .norm = {.tv_sec = LONG_MIN, .tv_nsec = -1 * (TIMESPEC_HZ - 1)},
+ }
+};
+
+/* Test cases for timespec_check_in_range */
+struct timespec_test_case check_cases[] = {
+ /* 0 - In range */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 1, .tv_nsec = 0},
+ .upper_bound = 1, .lower_bound = 1, .result = 1,
+ },
+ /* 1 - Out of range */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 2, .tv_nsec = 0},
+ .upper_bound = 1, .lower_bound = 1, .result = 0,
+ },
+ /* 2 - Upper Bound */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 2, .tv_nsec = 0},
+ .upper_bound = 2, .lower_bound = 1, .result = 1,
+ },
+ /* 3 - Lower Bound */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 0, .tv_nsec = 0},
+ .upper_bound = 1, .lower_bound = 0, .result = 1,
+ },
+ /* 4 - Out of range by nanosecs */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 1, .tv_nsec = 500},
+ .upper_bound = 1, .lower_bound = 1, .result = 0,
+ },
+ /* 5 - In range by nanosecs */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 1, .tv_nsec = 50000},
+ .upper_bound = 1.3, .lower_bound = 1, .result = 1,
+ },
+ /* 6 - Big nanosecs */
+ {.expected = {.tv_sec = 1, .tv_nsec = 0},
+ .observed = {.tv_sec = 0, .tv_nsec = 4000000},
+ .upper_bound = 1, .lower_bound = .001, .result = 1,
+ },
+ /* 7 - In range Negative values */
+ {.expected = {.tv_sec = -1, .tv_nsec = 0},
+ .observed = {.tv_sec = -1, .tv_nsec = 0},
+ .upper_bound = 1, .lower_bound = 1, .result = 1,
+ },
+ /* 8 - Out of range Negative values */
+ {.expected = {.tv_sec = -1, .tv_nsec = 0},
+ .observed = {.tv_sec = -1, .tv_nsec = 0},
+ .upper_bound = -1, .lower_bound = -1, .result = 0,
+ },
+ /* 9 - Negative values with negative nanosecs */
+ {.expected = {.tv_sec = -1, .tv_nsec = 0},
+ .observed = {.tv_sec = -1, .tv_nsec = -2000},
+ .upper_bound = 1, .lower_bound = 1, .result = 0,
+ },
+ /* 10 - Strict bounds */
+ {.expected = {.tv_sec = -1, .tv_nsec = 0},
+ .observed = {.tv_sec = -1, .tv_nsec = -20000},
+ .upper_bound = 1.00002, .lower_bound = 1.0000191, .result = 1,
+ },
+ /* 11 - Strict bounds with loose upper bound */
+ {.expected = {.tv_sec = 1, .tv_nsec = 20000},
+ .observed = {.tv_sec = 1, .tv_nsec = 30000},
+ .upper_bound = 1.0000100000, .lower_bound = 1.0000099998, .result = 1,
+ },
+ /* 12 - Strict bounds with loose lower bound */
+ {.expected = {.tv_sec = 1, .tv_nsec = 20000},
+ .observed = {.tv_sec = 1, .tv_nsec = 30000},
+ .upper_bound = 1.0000099999, .lower_bound = 1.00000999979, .result = 1,
+ },
+ /* 13 - Strict bounds highest precision */
+ {.expected = {.tv_sec = 1, .tv_nsec = 20000},
+ .observed = {.tv_sec = 1, .tv_nsec = 30000},
+ .upper_bound = 1.00000999980001, .lower_bound = 1.00000999979999, .result = 1,
+ },
+ /* Maximum/Minimum long values */
+ /* 14 */
+ {.expected = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ - 1},
+ .observed = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ - 2},
+ .upper_bound = 1, .lower_bound = .9, .result = 1,
+ },
+ /* 15 - support_timespec_ns overflow */
+ {.expected = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ},
+ .observed = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ},
+ .upper_bound = 1, .lower_bound = 1, .result = 1,
+ },
+ /* 16 - support_timespec_ns overflow + underflow */
+ {.expected = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ},
+ .observed = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ},
+ .upper_bound = 1, .lower_bound = 1, .result = 0,
+ },
+ /* 17 - support_timespec_ns underflow */
+ {.expected = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ},
+ .observed = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ},
+ .upper_bound = 1, .lower_bound = 1, .result = 1,
+ },
+ /* 18 - support_timespec_ns underflow + overflow */
+ {.expected = {.tv_sec = LONG_MIN, .tv_nsec = -TIMESPEC_HZ},
+ .observed = {.tv_sec = LONG_MAX, .tv_nsec = TIMESPEC_HZ},
+ .upper_bound = 1, .lower_bound = 1, .result = 0,
+ },
+ /* 19 - Biggest division */
+ {.expected = {.tv_sec = LONG_MAX / TIMESPEC_HZ , .tv_nsec = TIMESPEC_HZ - 1},
+ .observed = {.tv_sec = 0, .tv_nsec = 1},
+ .upper_bound = 1, .lower_bound = 1.0842021724855044e-19, .result = 1,
+ },
+ /* 20 - Lowest division */
+ {.expected = {.tv_sec = 0, .tv_nsec = 1},
+ .observed = {.tv_sec = LONG_MAX / TIMESPEC_HZ , .tv_nsec = TIMESPEC_HZ - 1},
+ .upper_bound = LONG_MAX, .lower_bound = 1, .result = 1,
+ },
+};
+
+static int
+do_test (void)
+{
+ int i = 0;
+ int ntests = sizeof (ns_cases) / sizeof (ns_cases[0]);
+
+ printf("Testing support_timespec_ns\n");
+ for (i = 0; i < ntests; i++)
+ {
+ TEST_COMPARE (support_timespec_ns (ns_cases[i].time),
+ ns_cases[i].time_ns);
+ }
+
+ ntests = sizeof (norm_cases) / sizeof (norm_cases[0]);
+ struct timespec result;
+ printf("Testing support_timespec_normalize\n");
+ for (i = 0; i < ntests; i++)
+ {
+ result = support_timespec_normalize (norm_cases[i].time);
+ TEST_COMPARE (norm_cases[i].norm.tv_sec, result.tv_sec);
+ TEST_COMPARE (norm_cases[i].norm.tv_nsec, result.tv_nsec);
+ }
+
+ ntests = sizeof (check_cases) / sizeof (check_cases[0]);
+ printf("Testing support_timespec_check_in_range\n");
+ for (i = 0; i < ntests; i++)
+ {
+ /* Its hard to find which test failed with just the TEST_COMPARE report.
+ So here we print every running testcase as well. */
+ printf("Test case %d\n", i);
+ TEST_COMPARE (support_timespec_check_in_range
+ (check_cases[i].expected, check_cases[i].observed,
+ check_cases[i].lower_bound,
+ check_cases[i].upper_bound), check_cases[i].result);
+ }
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/time/tst-cpuclock1.c b/time/tst-cpuclock1.c
index 0120906..1ac611a 100644
--- a/time/tst-cpuclock1.c
+++ b/time/tst-cpuclock1.c
@@ -26,6 +26,7 @@
#include <signal.h>
#include <stdint.h>
#include <sys/wait.h>
+#include <support/timespec.h>
/* This function is intended to rack up both user and system time. */
static void
@@ -155,16 +156,12 @@ do_test (void)
printf ("live PID %d after sleep => %ju.%.9ju\n",
child, (uintmax_t) after.tv_sec, (uintmax_t) after.tv_nsec);
- struct timespec diff = { .tv_sec = after.tv_sec - before.tv_sec,
- .tv_nsec = after.tv_nsec - before.tv_nsec };
- if (diff.tv_nsec < 0)
- {
- --diff.tv_sec;
- diff.tv_nsec += 1000000000;
- }
- if (diff.tv_sec != 0
- || diff.tv_nsec > 600000000
- || diff.tv_nsec < 100000000)
+ /* The bound values are empirically defined by testing this code over high cpu
+ usage and different nice values. Of all the values we keep the 90th
+ percentile of values and use those values for our testing allowed range. */
+ struct timespec diff = timespec_sub (support_timespec_normalize (after),
+ support_timespec_normalize (before));
+ if (!support_timespec_check_in_range (sleeptime, diff, .9, 1.1))
{
printf ("before - after %ju.%.9ju outside reasonable range\n",
(uintmax_t) diff.tv_sec, (uintmax_t) diff.tv_nsec);
@@ -194,19 +191,16 @@ do_test (void)
}
else
{
- struct timespec d = { .tv_sec = afterns.tv_sec - after.tv_sec,
- .tv_nsec = afterns.tv_nsec - after.tv_nsec };
- if (d.tv_nsec < 0)
- {
- --d.tv_sec;
- d.tv_nsec += 1000000000;
- }
- if (d.tv_sec > 0
- || d.tv_nsec < sleeptime.tv_nsec
- || d.tv_nsec > sleeptime.tv_nsec * 2)
+ /* The bound values are empirically defined by testing this code over
+ high cpu usage and different nice values. Of all the values we keep
+ the 90th percentile of values and use those values for our testing
+ allowed range. */
+ diff = timespec_sub (support_timespec_normalize (afterns),
+ support_timespec_normalize (after));
+ if (!support_timespec_check_in_range (sleeptime, diff, .9, 1.2))
{
printf ("nanosleep time %ju.%.9ju outside reasonable range\n",
- (uintmax_t) d.tv_sec, (uintmax_t) d.tv_nsec);
+ (uintmax_t) diff.tv_sec, (uintmax_t) diff.tv_nsec);
result = 1;
}
}
@@ -240,15 +234,13 @@ do_test (void)
/* Should be close to 0.6. */
printf ("dead PID %d => %ju.%.9ju\n",
child, (uintmax_t) dead.tv_sec, (uintmax_t) dead.tv_nsec);
-
- diff.tv_sec = dead.tv_sec - after.tv_sec;
- diff.tv_nsec = dead.tv_nsec - after.tv_nsec;
- if (diff.tv_nsec < 0)
- {
- --diff.tv_sec;
- diff.tv_nsec += 1000000000;
- }
- if (diff.tv_sec != 0 || diff.tv_nsec > 200000000)
+ /* The bound values are empirically defined by testing this code over high cpu
+ usage and different nice values. Of all the values we keep the 90th
+ percentile of values and use those values for our testing allowed range. */
+ diff = timespec_sub (support_timespec_normalize (dead),
+ support_timespec_normalize (after));
+ sleeptime.tv_nsec = 100000000;
+ if (!support_timespec_check_in_range (sleeptime, diff, .9, 1.2))
{
printf ("dead - after %ju.%.9ju outside reasonable range\n",
(uintmax_t) diff.tv_sec, (uintmax_t) diff.tv_nsec);