]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib, lib-settings: Move string-parse functions for bool/size/interval into lib
authorKarl Fleischmann <karl.fleischmann@open-xchange.com>
Wed, 26 Oct 2022 14:57:08 +0000 (16:57 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Mon, 21 Nov 2022 17:50:26 +0000 (17:50 +0000)
Including the tests. Call the new functions from the existing
settings-functions. These will be removed in a later commit.

src/lib-settings/settings-parser.c
src/lib-settings/settings-parser.h
src/lib-settings/test-settings-parser.c
src/lib/Makefile.am
src/lib/str-parse.c [new file with mode: 0644]
src/lib/str-parse.h [new file with mode: 0644]
src/lib/test-lib.inc
src/lib/test-str-parse.c [new file with mode: 0644]

index a64ee66cb194bb3e442bb4d0a31af2a5049c03a8..ab5f9008a2bcae90d2ea5b4de98129c5ec46593d 100644 (file)
@@ -9,12 +9,12 @@
 #include "execv-const.h"
 #include "str.h"
 #include "strescape.h"
+#include "str-parse.h"
 #include "var-expand.h"
 #include "settings-parser.h"
 
 #include <stdio.h>
 #include <unistd.h>
-#include <ctype.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
@@ -302,19 +302,7 @@ setting_define_find(const struct setting_parser_info *info, const char *key)
 int settings_get_bool(const char *value, bool *result_r,
                      const char **error_r)
 {
-       /* FIXME: eventually we'd want to support only yes/no */
-       if (strcasecmp(value, "yes") == 0 ||
-           strcasecmp(value, "y") == 0 || strcmp(value, "1") == 0)
-               *result_r = TRUE;
-       else if (strcasecmp(value, "no") == 0)
-               *result_r = FALSE;
-       else {
-               *error_r = t_strdup_printf("Invalid boolean value: %s (use yes or no)",
-                                          value);
-               return -1;
-       }
-
-       return 0;
+       return str_parse_get_bool(value, result_r, error_r);
 }
 
 static int
@@ -357,146 +345,22 @@ get_octal(struct setting_parser_context *ctx, const char *value,
        return 0;
 }
 
-static int settings_get_time_full(const char *str, unsigned int *interval_r,
-                                 bool milliseconds, const char **error_r)
-{
-       uintmax_t num, multiply = milliseconds ? 1000 : 1;
-       const char *p;
-
-       if (str_parse_uintmax(str, &num, &p) < 0) {
-               *error_r = t_strconcat("Invalid time interval: ", str, NULL);
-               return -1;
-       }
-       while (*p == ' ') p++;
-       if (*p == '\0' && num != 0) {
-               *error_r = t_strdup_printf("Time interval '%s' is missing units "
-                       "(add e.g. 's' for seconds)", str);
-               return -1;
-       }
-       switch (i_toupper(*p)) {
-       case 'S':
-               multiply *= 1;
-               if (str_begins_icase_with("secs", p) ||
-                   str_begins_icase_with("seconds", p))
-                       p = "";
-               break;
-       case 'M':
-               multiply *= 60;
-               if (str_begins_icase_with("mins", p) ||
-                   str_begins_icase_with("minutes", p))
-                       p = "";
-               else if (str_begins_icase_with("msecs", p) ||
-                        str_begins_icase_with("mseconds", p) ||
-                        str_begins_icase_with("millisecs", p) ||
-                        str_begins_icase_with("milliseconds", p)) {
-                       if (milliseconds || (num % 1000) == 0) {
-                               if (!milliseconds) {
-                                       /* allow ms also for seconds, as long
-                                          as it's divisible by seconds */
-                                       num /= 1000;
-                               }
-                               multiply = 1;
-                               p = "";
-                               break;
-                       }
-                       *error_r = t_strdup_printf(
-                               "Milliseconds not supported for this setting: %s", str);
-                       return -1;
-               }
-               break;
-       case 'H':
-               multiply *= 60*60;
-               if (str_begins_icase_with("hours", p))
-                       p = "";
-               break;
-       case 'D':
-               multiply *= 60*60*24;
-               if (str_begins_icase_with("days", p))
-                       p = "";
-               break;
-       case 'W':
-               multiply *= 60*60*24*7;
-               if (str_begins_icase_with("weeks", p))
-                       p = "";
-               break;
-       }
-
-       if (*p != '\0') {
-               *error_r = t_strconcat("Invalid time interval: ", str, NULL);
-               return -1;
-       }
-       if (num > UINT_MAX / multiply) {
-               *error_r = t_strconcat("Time interval is too large: ",
-                                      str, NULL);
-               return -1;
-       }
-       *interval_r = num * multiply;
-       return 0;
-}
-
 int settings_get_time(const char *str, unsigned int *secs_r,
                      const char **error_r)
 {
-       return settings_get_time_full(str, secs_r, FALSE, error_r);
+       return str_parse_get_interval(str, secs_r, error_r);
 }
 
 int settings_get_time_msecs(const char *str, unsigned int *msecs_r,
                            const char **error_r)
 {
-       return settings_get_time_full(str, msecs_r, TRUE, error_r);
+       return str_parse_get_interval_msecs(str, msecs_r, error_r);
 }
 
 int settings_get_size(const char *str, uoff_t *bytes_r,
                      const char **error_r)
 {
-       uintmax_t num, multiply = 1;
-       const char *p;
-
-       if (str_parse_uintmax(str, &num, &p) < 0) {
-               *error_r = t_strconcat("Invalid size: ", str, NULL);
-               return -1;
-       }
-       while (*p == ' ') p++;
-       switch (i_toupper(*p)) {
-       case 'B':
-               multiply = 1;
-               p += 1;
-               break;
-       case 'K':
-               multiply = 1024;
-               p += 1;
-               break;
-       case 'M':
-               multiply = 1024*1024;
-               p += 1;
-               break;
-       case 'G':
-               multiply = 1024*1024*1024;
-               p += 1;
-               break;
-       case 'T':
-               multiply = 1024ULL*1024*1024*1024;
-               p += 1;
-               break;
-       }
-
-       if (multiply > 1) {
-               /* Allow: k, ki, kiB */
-               if (i_toupper(*p) == 'I')
-                       p++;
-               if (i_toupper(*p) == 'B')
-                       p++;
-       }
-       if (*p != '\0') {
-               *error_r = t_strconcat("Invalid size: ", str, NULL);
-               return -1;
-       }
-       if (num > (UOFF_T_MAX) / multiply) {
-               *error_r = t_strconcat("Size is too large: ", str, NULL);
-               return -1;
-       }
-       *bytes_r = num * multiply;
-       return 0;
+       return str_parse_get_size(str, bytes_r, error_r);
 }
 
 static int get_enum(struct setting_parser_context *ctx, const char *value,
index 81ce318aa10d2f999d613bf584a596f58fff7ba1..24d21c9bc68777b13db183bcaa58920996f728f4 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef SETTINGS_PARSER_H
 #define SETTINGS_PARSER_H
 
+#include "str-parse.h"
+
 struct var_expand_table;
 struct var_expand_func_table;
 
index 83aefbafce645277eb25be6ac73ad12a8af6807e..d16b263fe1a91bb8c1c0b1dced32f167e5859a40 100644 (file)
@@ -28,199 +28,6 @@ static const char *test_settings_blobs[] =
        "\n",
 };
 
-
-static void test_settings_get_time(void)
-{
-       static const struct {
-               const char *input;
-               unsigned int output;
-       } tests[] = {
-               { "0", 0 },
-
-               { "59s", 59 },
-               { "59 s", 59 },
-               { "59se", 59 },
-               { "59sec", 59 },
-               { "59secs", 59 },
-               { "59seco", 59 },
-               { "59secon", 59 },
-               { "59second", 59 },
-               { "59seconds", 59 },
-               { "123456   seconds", 123456 },
-
-               { "123m", 123*60 },
-               { "123 m", 123*60 },
-               { "123 mi", 123*60 },
-               { "123 min", 123*60 },
-               { "123 mins", 123*60 },
-               { "123 minu", 123*60 },
-               { "123 minut", 123*60 },
-               { "123 minute", 123*60 },
-               { "123 minutes", 123*60 },
-
-               { "123h", 123*60*60 },
-               { "123 h", 123*60*60 },
-               { "123 ho", 123*60*60 },
-               { "123 hou", 123*60*60 },
-               { "123 hour", 123*60*60 },
-               { "123 hours", 123*60*60 },
-
-               { "12d", 12*60*60*24 },
-               { "12 d", 12*60*60*24 },
-               { "12 da", 12*60*60*24 },
-               { "12 day", 12*60*60*24 },
-               { "12 days", 12*60*60*24 },
-
-               { "3w", 3*60*60*24*7 },
-               { "3 w", 3*60*60*24*7 },
-               { "3 we", 3*60*60*24*7 },
-               { "3 wee", 3*60*60*24*7 },
-               { "3 week", 3*60*60*24*7 },
-               { "3 weeks", 3*60*60*24*7 },
-
-               { "1000ms", 1 },
-               { "50000ms", 50 },
-       };
-       struct {
-               const char *input;
-               unsigned int output;
-       } msecs_tests[] = {
-               { "0ms", 0 },
-               { "1ms", 1 },
-               { "123456ms", 123456 },
-               { "123456 ms", 123456 },
-               { "123456mse", 123456 },
-               { "123456msec", 123456 },
-               { "123456msecs", 123456 },
-               { "123456mseco", 123456 },
-               { "123456msecon", 123456 },
-               { "123456msecond", 123456 },
-               { "123456mseconds", 123456 },
-               { "123456mil", 123456 },
-               { "123456mill", 123456 },
-               { "123456milli", 123456 },
-               { "123456millis", 123456 },
-               { "123456millisec", 123456 },
-               { "123456millisecs", 123456 },
-               { "123456milliseco", 123456 },
-               { "123456millisecon", 123456 },
-               { "123456millisecond", 123456 },
-               { "123456milliseconds", 123456 },
-               { "4294967295 ms", 4294967295 },
-       };
-       const char *secs_errors[] = {
-               "-1",
-               "1",
-               /* wrong spellings: */
-               "1ss",
-               "1secss",
-               "1secondss",
-               "1ma",
-               "1minsa",
-               "1hu",
-               "1hoursa",
-               "1dd",
-               "1days?",
-               "1wa",
-               "1weeksb",
-
-               /* milliseconds: */
-               "1ms",
-               "999ms",
-               "1001ms",
-               /* overflows: */
-               "7102 w",
-               "4294967296 s",
-       };
-       const char *msecs_errors[] = {
-               "-1",
-               "1",
-               /* wrong spellings: */
-               "1mis",
-               "1mss",
-               /* overflows: */
-               "8 w",
-               "4294967296 ms",
-       };
-       unsigned int i, secs, msecs;
-       const char *error;
-
-       test_begin("settings_get_time()");
-       for (i = 0; i < N_ELEMENTS(tests); i++) {
-               test_assert_idx(settings_get_time(tests[i].input, &secs, &error) == 0, i);
-               test_assert_idx(secs == tests[i].output, i);
-
-               test_assert_idx(settings_get_time_msecs(tests[i].input, &msecs, &error) == 0, i);
-               test_assert_idx(msecs == tests[i].output*1000, i);
-       }
-       for (i = 0; i < N_ELEMENTS(msecs_tests); i++) {
-               test_assert_idx(settings_get_time_msecs(msecs_tests[i].input, &msecs, &error) == 0, i);
-               test_assert_idx(msecs == msecs_tests[i].output, i);
-       }
-       for (i = 0; i < N_ELEMENTS(secs_errors); i++)
-               test_assert_idx(settings_get_time(secs_errors[i], &secs, &error) < 0, i);
-       for (i = 0; i < N_ELEMENTS(msecs_errors); i++)
-               test_assert_idx(settings_get_time_msecs(msecs_errors[i], &msecs, &error) < 0, i);
-       test_end();
-}
-
-static void test_settings_get_size(void)
-{
-       test_begin("settings_get_size()");
-
-       static const struct {
-               const char *input;
-               uoff_t output;
-       } tests[] = {
-               { "0", 0 },
-               { "0000", 0 },
-               { "1b", 1 },
-               { "1B", 1 },
-               { "1 b", 1 },
-               { "1k", 1024 },
-               { "1K", 1024 },
-               { "1 k", 1024 },
-               { "1m", 1024*1024 },
-               { "1M", 1024*1024 },
-               { "1 m", 1024*1024 },
-               { "1g", 1024*1024*1024ULL },
-               { "1G", 1024*1024*1024ULL },
-               { "1 g", 1024*1024*1024ULL },
-               { "1t", 1024*1024*1024*1024ULL },
-               { "1T", 1024*1024*1024*1024ULL },
-               { "1 t", 1024*1024*1024*1024ULL },
-       };
-
-       const char *size_errors[] = {
-               "-1",
-               "one",
-               "",
-               "340282366920938463463374607431768211456",
-               "2^32",
-               "2**32",
-               "1e10",
-               "1 byte",
-       };
-
-       size_t i;
-       uoff_t size;
-       const char *error;
-
-       for (i = 0; i < N_ELEMENTS(tests); i++) {
-               error = NULL;
-               test_assert_idx(settings_get_size(tests[i].input, &size, &error) == 0, i);
-               test_assert_idx(size == tests[i].output, i);
-               test_assert(error == NULL);
-       }
-       for (i = 0; i < N_ELEMENTS(size_errors); i++) {
-               error = NULL;
-               test_assert_idx(settings_get_size(size_errors[i], &size, &error) < 0, i);
-               test_assert(error != NULL);
-       };
-
-       test_end();
-}
-
 static void test_settings_parser_get(void)
 {
        struct test_settings {
@@ -331,8 +138,6 @@ static void test_settings_parser_get(void)
 int main(void)
 {
        static void (*const test_functions[])(void) = {
-               test_settings_get_time,
-               test_settings_get_size,
                test_settings_parser_get,
                NULL
        };
index e7adbbc48470d8d5ada8faada43020d55c9e2be1..817a6dfffb9e8c9775d33ebdc251d05a08cbabf7 100644 (file)
@@ -187,6 +187,7 @@ liblib_la_SOURCES = \
        str.c \
        str-find.c \
        str-sanitize.c \
+       str-parse.c \
        str-table.c \
        strescape.c \
        strfuncs.c \
@@ -346,6 +347,7 @@ headers = \
        str.h \
        str-find.h \
        str-sanitize.h \
+       str-parse.h \
        str-table.h \
        strescape.h \
        strfuncs.h \
@@ -462,6 +464,7 @@ test_lib_SOURCES = \
        test-strnum.c \
        test-str-find.c \
        test-str-sanitize.c \
+       test-str-parse.c \
        test-str-table.c \
        test-time-util.c \
        test-unichar.c \
diff --git a/src/lib/str-parse.c b/src/lib/str-parse.c
new file mode 100644 (file)
index 0000000..34068c9
--- /dev/null
@@ -0,0 +1,167 @@
+/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str-parse.h"
+
+#include <ctype.h>
+
+static int str_parse_get_interval_full(
+       const char *str, unsigned int *interval_r, bool milliseconds,
+       const char **error_r)
+{
+       uintmax_t num, multiply = milliseconds ? 1000 : 1;
+       const char *p;
+
+       if (str_parse_uintmax(str, &num, &p) < 0) {
+               *error_r = t_strconcat("Invalid time interval: ", str, NULL);
+               return -1;
+       }
+       while (*p == ' ') p++;
+       if (*p == '\0' && num != 0) {
+               *error_r = t_strdup_printf("Time interval '%s' is missing units "
+                       "(add e.g. 's' for seconds)", str);
+               return -1;
+       }
+       switch (i_toupper(*p)) {
+       case 'S':
+               multiply *= 1;
+               if (str_begins_icase_with("secs", p) ||
+                   str_begins_icase_with("seconds", p))
+                       p = "";
+               break;
+       case 'M':
+               multiply *= 60;
+               if (str_begins_icase_with("mins", p) ||
+                   str_begins_icase_with("minutes", p))
+                       p = "";
+               else if (str_begins_icase_with("msecs", p) ||
+                        str_begins_icase_with("mseconds", p) ||
+                        str_begins_icase_with("millisecs", p) ||
+                        str_begins_icase_with("milliseconds", p)) {
+                       if (milliseconds || (num % 1000) == 0) {
+                               if (!milliseconds) {
+                                       /* allow ms also for seconds, as long
+                                          as it's divisible by seconds */
+                                       num /= 1000;
+                               }
+                               multiply = 1;
+                               p = "";
+                               break;
+                       }
+                       *error_r = t_strdup_printf(
+                               "Milliseconds not supported for this setting: %s", str);
+                       return -1;
+               }
+               break;
+       case 'H':
+               multiply *= 60*60;
+               if (str_begins_icase_with("hours", p))
+                       p = "";
+               break;
+       case 'D':
+               multiply *= 60*60*24;
+               if (str_begins_icase_with("days", p))
+                       p = "";
+               break;
+       case 'W':
+               multiply *= 60*60*24*7;
+               if (str_begins_icase_with("weeks", p))
+                       p = "";
+               break;
+       }
+
+       if (*p != '\0') {
+               *error_r = t_strconcat("Invalid time interval: ", str, NULL);
+               return -1;
+       }
+       if (num > UINT_MAX / multiply) {
+               *error_r = t_strconcat("Time interval is too large: ",
+                                      str, NULL);
+               return -1;
+       }
+       *interval_r = num * multiply;
+       return 0;
+}
+
+int str_parse_get_interval(const char *str, unsigned int *secs_r,
+                          const char **error_r)
+{
+       return str_parse_get_interval_full(str, secs_r, FALSE, error_r);
+}
+
+int str_parse_get_interval_msecs(const char *str, unsigned int *msecs_r,
+                                const char **error_r)
+{
+       return str_parse_get_interval_full(str, msecs_r, TRUE, error_r);
+}
+
+int str_parse_get_size(const char *str, uoff_t *bytes_r,
+                      const char **error_r)
+{
+       uintmax_t num, multiply = 1;
+       const char *p;
+
+       if (str_parse_uintmax(str, &num, &p) < 0) {
+               *error_r = t_strconcat("Invalid size: ", str, NULL);
+               return -1;
+       }
+       while (*p == ' ') p++;
+       switch (i_toupper(*p)) {
+       case 'B':
+               multiply = 1;
+               p += 1;
+               break;
+       case 'K':
+               multiply = 1024;
+               p += 1;
+               break;
+       case 'M':
+               multiply = 1024*1024;
+               p += 1;
+               break;
+       case 'G':
+               multiply = 1024*1024*1024;
+               p += 1;
+               break;
+       case 'T':
+               multiply = 1024ULL*1024*1024*1024;
+               p += 1;
+               break;
+       }
+
+       if (multiply > 1) {
+               /* Allow: k, ki, kiB */
+               if (i_toupper(*p) == 'I')
+                       p++;
+               if (i_toupper(*p) == 'B')
+                       p++;
+       }
+       if (*p != '\0') {
+               *error_r = t_strconcat("Invalid size: ", str, NULL);
+               return -1;
+       }
+       if (num > (UOFF_T_MAX) / multiply) {
+               *error_r = t_strconcat("Size is too large: ", str, NULL);
+               return -1;
+       }
+       *bytes_r = num * multiply;
+       return 0;
+}
+
+int str_parse_get_bool(const char *value, bool *result_r,
+                      const char **error_r)
+{
+       /* FIXME: eventually we'd want to support only yes/no */
+       if (strcasecmp(value, "yes") == 0 ||
+           strcasecmp(value, "y") == 0 || strcmp(value, "1") == 0)
+               *result_r = TRUE;
+       else if (strcasecmp(value, "no") == 0)
+               *result_r = FALSE;
+       else {
+               *error_r = t_strdup_printf("Invalid boolean value: %s (use yes or no)",
+                                          value);
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/src/lib/str-parse.h b/src/lib/str-parse.h
new file mode 100644 (file)
index 0000000..882504f
--- /dev/null
@@ -0,0 +1,18 @@
+/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */
+#ifndef STR_PARSE_H
+#define STR_PARSE_H
+
+/* Parse time interval string, return as seconds. */
+int str_parse_get_interval(const char *str, unsigned int *secs_r,
+                          const char **error_r);
+/* Parse time interval string, return as milliseconds. */
+int str_parse_get_interval_msecs(const char *str, unsigned int *msecs_r,
+                                const char **error_r);
+/* Parse size string, return as bytes. */
+int str_parse_get_size(const char *str, uoff_t *bytes_r,
+                      const char **error_r);
+/* Parse boolean string, return as boolean */
+int str_parse_get_bool(const char *value, bool *result_r,
+                      const char **error_r);
+
+#endif // STR_PARSE_H
index d9b9c794ac0f3cba79cac36503c452b36b0a2c44..557d753a4319044da44a150b68255cec98b5c22a 100644 (file)
@@ -103,6 +103,7 @@ TEST(test_strfuncs)
 FATAL(fatal_strfuncs)
 TEST(test_strnum)
 TEST(test_str_find)
+TEST(test_str_parse)
 TEST(test_str_sanitize)
 TEST(test_str_table)
 TEST(test_time_util)
diff --git a/src/lib/test-str-parse.c b/src/lib/test-str-parse.c
new file mode 100644 (file)
index 0000000..17e03bf
--- /dev/null
@@ -0,0 +1,209 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str-parse.h"
+
+static void test_str_parse_get_interval(void)
+{
+       static const struct {
+               const char *input;
+               unsigned int output;
+       } tests[] = {
+               { "0", 0 },
+
+               { "59s", 59 },
+               { "59 s", 59 },
+               { "59se", 59 },
+               { "59sec", 59 },
+               { "59secs", 59 },
+               { "59seco", 59 },
+               { "59secon", 59 },
+               { "59second", 59 },
+               { "59seconds", 59 },
+               { "123456   seconds", 123456 },
+
+               { "123m", 123*60 },
+               { "123 m", 123*60 },
+               { "123 mi", 123*60 },
+               { "123 min", 123*60 },
+               { "123 mins", 123*60 },
+               { "123 minu", 123*60 },
+               { "123 minut", 123*60 },
+               { "123 minute", 123*60 },
+               { "123 minutes", 123*60 },
+
+               { "123h", 123*60*60 },
+               { "123 h", 123*60*60 },
+               { "123 ho", 123*60*60 },
+               { "123 hou", 123*60*60 },
+               { "123 hour", 123*60*60 },
+               { "123 hours", 123*60*60 },
+
+               { "12d", 12*60*60*24 },
+               { "12 d", 12*60*60*24 },
+               { "12 da", 12*60*60*24 },
+               { "12 day", 12*60*60*24 },
+               { "12 days", 12*60*60*24 },
+
+               { "3w", 3*60*60*24*7 },
+               { "3 w", 3*60*60*24*7 },
+               { "3 we", 3*60*60*24*7 },
+               { "3 wee", 3*60*60*24*7 },
+               { "3 week", 3*60*60*24*7 },
+               { "3 weeks", 3*60*60*24*7 },
+
+               { "1000ms", 1 },
+               { "50000ms", 50 },
+       };
+       struct {
+               const char *input;
+               unsigned int output;
+       } msecs_tests[] = {
+               { "0ms", 0 },
+               { "1ms", 1 },
+               { "123456ms", 123456 },
+               { "123456 ms", 123456 },
+               { "123456mse", 123456 },
+               { "123456msec", 123456 },
+               { "123456msecs", 123456 },
+               { "123456mseco", 123456 },
+               { "123456msecon", 123456 },
+               { "123456msecond", 123456 },
+               { "123456mseconds", 123456 },
+               { "123456mil", 123456 },
+               { "123456mill", 123456 },
+               { "123456milli", 123456 },
+               { "123456millis", 123456 },
+               { "123456millisec", 123456 },
+               { "123456millisecs", 123456 },
+               { "123456milliseco", 123456 },
+               { "123456millisecon", 123456 },
+               { "123456millisecond", 123456 },
+               { "123456milliseconds", 123456 },
+               { "4294967295 ms", 4294967295 },
+       };
+       const char *secs_errors[] = {
+               "-1",
+               "1",
+               /* wrong spellings: */
+               "1ss",
+               "1secss",
+               "1secondss",
+               "1ma",
+               "1minsa",
+               "1hu",
+               "1hoursa",
+               "1dd",
+               "1days?",
+               "1wa",
+               "1weeksb",
+
+               /* milliseconds: */
+               "1ms",
+               "999ms",
+               "1001ms",
+               /* overflows: */
+               "7102 w",
+               "4294967296 s",
+       };
+       const char *msecs_errors[] = {
+               "-1",
+               "1",
+               /* wrong spellings: */
+               "1mis",
+               "1mss",
+               /* overflows: */
+               "8 w",
+               "4294967296 ms",
+       };
+       unsigned int i, secs, msecs;
+       const char *error;
+
+       test_begin("str_parse_get_interval()");
+       for (i = 0; i < N_ELEMENTS(tests); i++) {
+               test_assert_idx(str_parse_get_interval(tests[i].input, &secs,
+                                                      &error) == 0, i);
+               test_assert_idx(secs == tests[i].output, i);
+
+               test_assert_idx(str_parse_get_interval_msecs(
+                                       tests[i].input, &msecs, &error) == 0, i);
+               test_assert_idx(msecs == tests[i].output*1000, i);
+       }
+       for (i = 0; i < N_ELEMENTS(msecs_tests); i++) {
+               test_assert_idx(str_parse_get_interval_msecs(
+                                       msecs_tests[i].input, &msecs, &error) == 0, i);
+               test_assert_idx(msecs == msecs_tests[i].output, i);
+       }
+       for (i = 0; i < N_ELEMENTS(secs_errors); i++)
+               test_assert_idx(str_parse_get_interval(secs_errors[i], &secs,
+                                                      &error) < 0, i);
+       for (i = 0; i < N_ELEMENTS(msecs_errors); i++)
+               test_assert_idx(str_parse_get_interval_msecs(
+                                       msecs_errors[i], &msecs, &error) < 0, i);
+       test_end();
+}
+
+static void test_str_parse_get_size(void)
+{
+       test_begin("str_parse_get_size()");
+
+       static const struct {
+               const char *input;
+               uoff_t output;
+       } tests[] = {
+               { "0", 0 },
+               { "0000", 0 },
+               { "1b", 1 },
+               { "1B", 1 },
+               { "1 b", 1 },
+               { "1k", 1024 },
+               { "1K", 1024 },
+               { "1 k", 1024 },
+               { "1m", 1024*1024 },
+               { "1M", 1024*1024 },
+               { "1 m", 1024*1024 },
+               { "1g", 1024*1024*1024ULL },
+               { "1G", 1024*1024*1024ULL },
+               { "1 g", 1024*1024*1024ULL },
+               { "1t", 1024*1024*1024*1024ULL },
+               { "1T", 1024*1024*1024*1024ULL },
+               { "1 t", 1024*1024*1024*1024ULL },
+       };
+
+       const char *size_errors[] = {
+               "-1",
+               "one",
+               "",
+               "340282366920938463463374607431768211456",
+               "2^32",
+               "2**32",
+               "1e10",
+               "1 byte",
+       };
+
+       size_t i;
+       uoff_t size;
+       const char *error;
+
+       for (i = 0; i < N_ELEMENTS(tests); i++) {
+               error = NULL;
+               test_assert_idx(str_parse_get_size(tests[i].input, &size,
+                                                  &error) == 0, i);
+               test_assert_idx(size == tests[i].output, i);
+               test_assert(error == NULL);
+       }
+       for (i = 0; i < N_ELEMENTS(size_errors); i++) {
+               error = NULL;
+               test_assert_idx(str_parse_get_size(size_errors[i], &size,
+                                                  &error) < 0, i);
+               test_assert(error != NULL);
+       };
+
+       test_end();
+}
+
+void test_str_parse(void)
+{
+       test_str_parse_get_interval();
+       test_str_parse_get_size();
+}