]> xenbits.xensource.com Git - people/royger/xen-test-framework.git/commitdiff
Integer subset of vsnprintf(), including 64bit types in 32bit build
authorAndrew Cooper <andrew.cooper3@citrix.com>
Mon, 23 Mar 2015 16:40:21 +0000 (17:40 +0100)
committerAndrew Cooper <andrew.cooper3@citrix.com>
Mon, 28 Sep 2015 13:53:02 +0000 (14:53 +0100)
Also a test binary which compares this implementation to the local libc's
implementation.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
.gitignore
Doxyfile
common/libc/vsnprintf.c [new file with mode: 0644]
config/files.mk
include/xtf/libc.h
selftests/Makefile [new file with mode: 0644]
selftests/vsnprintf.c [new file with mode: 0644]

index a63215ee12bcfb857dfd27fe34b8a06556c3b50b..732c34780d7f25f4b036a5f5456bfa3a0b1f4f35 100644 (file)
@@ -5,4 +5,5 @@
 /cscope.*
 /dist/
 /docs/autogenerated/
+/selftests/test-*
 /tests/*/test-*
index 99e9d24b91d7d8c9038cd352e58c9452952490a0..9d6f4a76aa7e217dd12bd9023d4c61e7658ed97b 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -828,7 +828,7 @@ RECURSIVE              = YES
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                = 
+EXCLUDE                = selftests
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
diff --git a/common/libc/vsnprintf.c b/common/libc/vsnprintf.c
new file mode 100644 (file)
index 0000000..4ac09e9
--- /dev/null
@@ -0,0 +1,416 @@
+#include <arch/x86/div.h>
+#include <xtf/libc.h>
+#include <xtf/compiler.h>
+
+#ifndef isdigit
+/* Avoid pulling in all of ctypes just for this. */
+static int isdigit(int c)
+{
+    return c >= '0' && c <= '9';
+}
+#endif
+
+/*
+ * The subset of formatting supported:
+ *
+ * From the C11 specification:
+ * A conversion specification is the '%' character followed by:
+ * - Zero or more flags: ('-', '+', ' ', '#', '0')
+ *   - '-' Left justified
+ *   - '+' Explicit sign
+ *   - ' ' Space instead of sign
+ *   - '#' Alternative representation
+ *   - '0' Zero-pad from the left
+ * - Optional minimum width field ('*' or integer)
+ * - Optional precision ('.' followed by '*' or integer)
+ * - Optional length modifier:
+ *   - 'hh' - char
+ *   - 'h'  - short
+ *   - 'l'  - long
+ *   - 'll' - long long
+ *   - 'z'  - size_t
+ * - Mandatory coversion specifier:
+ *   - signed integer ('d', 'i')
+ *   - unsigned integer ('o', 'u', 'x', 'X' uppercase)
+ *   - unsigned char ('c')
+ *   - array of char ('s')
+ *   - pointer to void ('p')
+ *   - literal '%'
+ */
+
+/* Flags */
+#define LEFT      (1u << 0)
+#define PLUS      (1u << 1)
+#define SPACE     (1u << 2)
+#define ALTERNATE (1u << 3)
+#define ZERO      (1u << 4)
+/* Conversions */
+#define UPPER     (1u << 5)
+#define SIGNED    (1u << 6)
+
+/* Shorthand for ensuring str moves forwards, but not overruning the buffer. */
+#define PUT(c)                                  \
+    ({ if ( str < end )                         \
+            *str = (c);                         \
+        ++str;                                  \
+    })
+
+static int fmt_int(const char **fmt)
+{
+    int res = 0;
+
+    while( isdigit(**fmt) )
+    {
+        res *= 10;
+        res += **fmt - '0';
+        ++*fmt;
+    }
+
+    return res;
+}
+
+static char *number(char *str, char *end, long long val, unsigned base,
+                    int width, int precision, unsigned flags)
+{
+    static const char lower[] = "0123456789abcdef";
+    static const char upper[] = "0123456789ABCDEF";
+    const char *digits = (flags & UPPER) ? upper : lower;
+
+    char tmp[24], prefix = '\0';
+    int i = 0;
+    uint64_t uval = val;
+
+    /* Sanity check base. */
+    if ( !(base == 8 || base == 10 || base == 16) )
+        return str;
+
+    /* Override zeropad if we are aligning left or have a specific precsion. */
+    if ( (flags & LEFT) || (precision != -1) )
+        flags &= ~ZERO;
+
+    /* Signed values only can get explicit svign treatment. */
+    if ( flags & SIGNED )
+    {
+        if ( val < 0 )
+        {
+            prefix = '-';
+            uval = -val;
+            width--;
+        }
+        else if ( flags & PLUS )
+        {
+            prefix = '+';
+            width--;
+        }
+        else if ( flags & SPACE )
+        {
+            prefix = ' ';
+            width--;
+        }
+    }
+
+    /* Alternate representation applies to oct/hex only. */
+    if ( flags & ALTERNATE )
+        switch ( base )
+        {
+        case 16: width -= 2; break;
+        case 8:  width -= 1; break;
+        }
+
+    /* Make sure we at least have a single '0'. */
+    if ( val == 0 )
+        tmp[i++] = '0';
+    else
+        /* tmp contains the number formatted backwards. */
+        while ( uval )
+            tmp[i++] = digits[divmod64(&uval, base)];
+
+    /* Expand precision if the number is too long. */
+    if ( i > precision )
+        precision = i;
+    width -= precision;
+
+    /* If we are doing nothing special, pad with ' ' on the LHS. */
+    if ( (flags & (LEFT|ZERO)) == 0 )
+        while ( width-- > 0 )
+            PUT(' ');
+
+    /* Optional sign character for signed numbers. */
+    if ( prefix )
+        PUT(prefix);
+
+    /* Optional leading '0x' or '0'. */
+    if ( flags & ALTERNATE )
+        switch ( base )
+        {
+        case 16: PUT('0'); PUT('x'); break;
+        case 8:  PUT('0'); break;
+        }
+
+    /* Zero pad at the start of the number. */
+    if ( flags & ZERO )
+        while ( width-- > 0 )
+            PUT('0');
+
+    /* If we have too fewer digits than precision, zero pad some more. */
+    while ( i < precision-- )
+        PUT('0');
+
+    /* Copy the number from tmp. */
+    while ( i-- )
+        PUT(tmp[i]);
+
+    /* Pad any remaining width on the RHS. */
+    while (width-- > 0)
+        PUT(' ');
+
+    return str;
+}
+
+int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+    char *str = buf, *end = buf + size;
+
+    for ( ; *fmt != '\0'; ++fmt )
+    {
+        const char *spec_start = fmt; /* For rewinding on error. */
+
+        unsigned long long num;
+        unsigned base, flags = 0;
+        int width = -1, precision = -1;
+        char length_mod = 'i';
+
+        /* Put regular characters into the destination. */
+        if ( *fmt != '%' )
+        {
+            PUT(*fmt);
+            continue;
+        }
+
+ next_flag: /* Process any flags. */
+        ++fmt;
+        switch ( *fmt )
+        {
+        case '-': flags |= LEFT;      goto next_flag;
+        case '+': flags |= PLUS;      goto next_flag;
+        case ' ': flags |= SPACE;     goto next_flag;
+        case '#': flags |= ALTERNATE; goto next_flag;
+        case '0': flags |= ZERO;      goto next_flag;
+        }
+
+        /* Process the width field. */
+        if ( *fmt == '*' )
+        {
+            ++fmt;
+            width = va_arg(args, int);
+            if ( width < 0 )
+            {
+                flags |= LEFT;
+                width = -width;
+            }
+        }
+        else if ( isdigit(*fmt) )
+        {
+            width = fmt_int(&fmt);
+            if ( width < 0 )
+            {
+                flags |= LEFT;
+                width = -width;
+            }
+        }
+
+        /* Process the precision field. */
+        if ( *fmt == '.' )
+        {
+            ++fmt;
+            if ( *fmt == '*' )
+            {
+                ++fmt;
+                precision = va_arg(args, int);
+            }
+            else if ( isdigit(*fmt) )
+                precision = fmt_int(&fmt);
+
+            /* Negative precision is meaningless */
+            if ( precision < 0 )
+                precision = -1;
+        }
+
+        /* Process the length modifier. */
+        switch ( *fmt )
+        {
+        case 'h': length_mod = 'h'; ++fmt; break;
+        case 'l': length_mod = 'l'; ++fmt; break;
+        case 'z': length_mod = 'z'; ++fmt; break;
+        }
+        /* Might be two... */
+        switch ( *fmt )
+        {
+        case 'h': length_mod = 'H'; ++fmt; break;
+        case 'l': length_mod = 'L'; ++fmt; break;
+        }
+
+        /* Process the conversion modifier. */
+        switch ( *fmt )
+        {
+        case '%': /* Literal '%'. */
+            PUT('%');
+            continue;
+
+        case 'c': /* Unsigned char. */
+        {
+            unsigned char c = va_arg(args, int);
+
+            if ( !(flags & LEFT) )
+                while ( --width > 0 )
+                    PUT(' ');
+
+            PUT(c);
+
+            while ( --width > 0 )
+                PUT(' ');
+
+            continue;
+        }
+
+        case 's': /* String. */
+        {
+            const char *s = va_arg(args, const char *);
+            int len, i;
+
+            if ( !s )
+                s = "(NULL)";
+
+            if ( precision < 0 )
+                len = strlen(s);
+            else
+                len = strnlen(s, precision);
+
+            if ( !(flags & LEFT) )
+                while ( len < width-- )
+                    PUT(' ');
+
+            for ( i = 0; i < len; ++i )
+                PUT(s[i]);
+
+            while ( len < width-- )
+                PUT(' ');
+
+            continue;
+        }
+
+        case 'p': /* Pointer. */
+        {
+            const void *p = va_arg(args, const void *);
+
+            if ( width == -1 )
+            {
+                width = 2 * sizeof(p);
+                flags |= ZERO;
+            }
+
+            str = number(str, end, (unsigned long)p,
+                         16, width, precision, flags);
+
+            continue;
+        }
+
+        default: /* Something unrecognised - print the specifier literally. */
+            PUT('%');
+            fmt = spec_start;
+            continue;
+
+        /* From here on down, all the numbers. */
+
+        case 'o': /* Octal. */
+            base = 8;
+            break;
+
+        case 'd': case 'i': /* Signed decimal. */
+            flags |= SIGNED;
+            /* fallthough */
+        case 'u': /* Unsigned decimal. */
+            base = 10;
+            break;
+
+        case 'X': /* Uppercase hex. */
+            flags |= UPPER;
+            /* fallthrough */
+        case 'x': /* Lowercase hex. */
+            base = 16;
+            break;
+        }
+
+        /* Pull the arg and cast correctly. */
+        switch ( length_mod )
+        {
+        case 'H':
+            if ( flags & SIGNED )
+                num = (signed char)va_arg(args, int);
+            else
+                num = (unsigned char)va_arg(args, int);
+            break;
+
+        case 'h':
+            if ( flags & SIGNED )
+                num = (signed short)va_arg(args, int);
+            else
+                num = (unsigned short)va_arg(args, int);
+            break;
+
+        case 'i':
+            if ( flags & SIGNED )
+                num = (signed int)va_arg(args, int);
+            else
+                num = (unsigned int)va_arg(args, int);
+            break;
+
+        case 'z':
+            num = (size_t)va_arg(args, size_t);
+            break;
+
+        case 'l':
+            if ( flags & SIGNED )
+                num = (signed long)va_arg(args, long);
+            else
+                num = (unsigned long)va_arg(args, unsigned long);
+            break;
+
+        case 'L':
+            if ( flags & SIGNED )
+                num = (signed long long)va_arg(args, long long);
+            else
+                num = (unsigned long long)va_arg(args, unsigned long long);
+            break;
+
+        default: /* Really shouldn't happen, but rewind just in case. */
+            PUT('%');
+            fmt = spec_start;
+            continue;
+        }
+
+        str = number(str, end, num, base, width, precision, flags);
+    }
+
+    /* NUL terminate the buffer, if there is room (but don't count '\0'). */
+    if ( str < end )
+        *str = '\0';
+    /*
+     * Or trucate at the final character if an overrun occurred and buf is not
+     * 0 length.
+     */
+    else if ( size > 0 )
+        end[-1] = '\0';
+
+    return str - buf;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
index 58286a66407ed1a4de68ed9a4b3411dd509a85e2..89575cd4523e2c98fead1a64ec8dbc86c69df6fb 100644 (file)
@@ -6,6 +6,7 @@
 
 obj-perarch += $(ROOT)/common/console.o
 obj-perarch += $(ROOT)/common/libc/string.o
+obj-perarch += $(ROOT)/common/libc/vsnprintf.o
 obj-perarch += $(ROOT)/common/setup.o
 
 obj-perenv += $(ROOT)/arch/x86/setup.o
index a794d0a4c96eebfbdfd23a4c6759157adde67e6e..73c7b57e069728969b9af798b13fa0069c21e76b 100644 (file)
@@ -18,6 +18,8 @@
 size_t strlen(const char *str);
 size_t strnlen(const char *str, size_t max);
 
+int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
+
 #endif /* XTF_LIBC_H */
 
 /*
diff --git a/selftests/Makefile b/selftests/Makefile
new file mode 100644 (file)
index 0000000..a9e1ca0
--- /dev/null
@@ -0,0 +1,26 @@
+ROOT := $(abspath $(CURDIR)/..)
+
+COMMON_CFLAGS := -Wall -Werror -Wextra -MMD -MP
+
+TESTS := test-vsnprintf32
+TESTS += test-vsnprintf64
+
+.PHONY: test
+test: $(TESTS)
+       @ set -e; for X in $(TESTS); \
+       do \
+               echo "Running $$X"; \
+               ./$$X; \
+       done
+
+test-vsnprintf32 : vsnprintf.c
+       $(CC) -m32 $(COMMON_CFLAGS) -I $(ROOT)/include -Wno-format -O3 $< -o $@
+
+test-vsnprintf64 : vsnprintf.c
+       $(CC) -m64 $(COMMON_CFLAGS) -I $(ROOT)/include -Wno-format -O3 $< -o $@
+
+-include $(TESTS:%=%.d)
+
+.PHONY: clean
+clean:
+       rm -f $(TESTS) *.o
diff --git a/selftests/vsnprintf.c b/selftests/vsnprintf.c
new file mode 100644 (file)
index 0000000..a90f6c3
--- /dev/null
@@ -0,0 +1,288 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+
+#define vsnprintf xtf_vsnprintf
+#include "../common/libc/vsnprintf.c"
+#undef vsnprintf
+
+/*
+ * To build and run:
+ *
+ * gcc -I include/ -Wall -Werror -Wextra -Wno-format -O3 -D_FORTIFY_SOURCE=2 test-vsnprintf.c -o test-vsnprintf
+ * valgrind --track-origins=yes ./test-vsnprintf
+ */
+
+static bool debug = false; /* Always print intermediate buffers? */
+
+static bool __attribute__((format(printf, 3, 4)))
+test(char *str, size_t size, const char *fmt, ...)
+{
+    size_t newsz = size + 2;
+    char libstr[newsz + 1];
+    char xtfstr[newsz + 1];
+    int librc, xtfrc;
+    va_list libargs, xtfargs;
+    bool ok;
+
+    /* Clobber destination buffers with '$'. */
+    memset(libstr, '$', newsz); libstr[newsz] = '\0';
+    memset(xtfstr, '$', newsz); xtfstr[newsz] = '\0';
+
+    va_start(libargs, fmt);
+    va_copy(xtfargs, libargs);
+
+    /* Format using the library. */
+    librc = vsnprintf(str ? libstr : NULL, size, fmt, libargs);
+    va_end(libargs);
+
+    /* Format using xtf. */
+    xtfrc = xtf_vsnprintf(str ? xtfstr : NULL, size, fmt, xtfargs);
+    va_end(xtfargs);
+
+    /* Are the results the same? */
+    ok = ( librc == xtfrc &&
+           memcmp(libstr, xtfstr, newsz) == 0 );
+
+    if ( debug || !ok )
+    {
+        printf("\n  lib: '%s', %d\n", libstr, librc);
+        printf("  xtf: '%s', %d\n", xtfstr, xtfrc);
+    }
+    else if ( ok )
+        printf("= '%s' - OK\n", xtfstr);
+
+    return ok;
+}
+
+/* Stringize the arguments, log them, then test them. */
+#define TEST(args...)                          \
+    ({ printf("case (%s): ", # args);          \
+        if (!test(args)) success = false;      \
+    })
+#define TEST_NONFATAL(args...)                 \
+    ({ printf("case (%s): ", # args);          \
+        test(args);                            \
+    })
+
+int main(void)
+{
+    bool success = true;
+    char buf[1];
+
+    printf("Testing xtf's vsnprintf() implementation against the local libc\n");
+
+    TEST(NULL, 0, "");
+    TEST(NULL, 0, "abc");
+    TEST(buf, 0, "abc");
+    TEST(buf, 1, "abc");
+    TEST(buf, 2, "abc");
+    TEST(buf, 3, "abc");
+    TEST(buf, 4, "abc");
+
+    TEST(buf, 0, "%%");
+    TEST(buf, 1, "%%");
+    TEST(buf, 2, "%%");
+    TEST(buf, 3, "%%");
+
+    TEST(buf, 0, "%s", NULL);
+    TEST(buf, 2, "%s", NULL);
+    TEST_NONFATAL(buf, 4, "%s", NULL);
+    TEST_NONFATAL(buf, 6, "%s", NULL);
+
+    TEST(buf, 0, "%s", "abc");
+    TEST(buf, 2, "%s", "abc");
+    TEST(buf, 4, "%s", "abc");
+    TEST(buf, 6, "%s", "abc");
+
+    TEST(buf, 30, "%3s", "abcd");
+    TEST(buf, 30, "%5s", "abcd");
+    TEST(buf, 30, "%7s", "abcd");
+
+    TEST(buf, 30, "%-3s", "abcd");
+    TEST(buf, 30, "%-5s", "abcd");
+    TEST(buf, 30, "%-7s", "abcd");
+
+    TEST(buf, 30, "%-3.1s", "abcd");
+    TEST(buf, 30, "%-5.2s", "abcd");
+    TEST(buf, 30, "%-7.3s", "abcd");
+
+    TEST(buf, 30, "%*s", 1, "abcd");
+    TEST(buf, 30, "%*s", 5, "abcd");
+    TEST(buf, 30, "%*s", -1, "abcd");
+    TEST(buf, 30, "%*s", -5, "abcd");
+
+    TEST(buf, 30, "%*.*s", 1, 1, "abcd");
+    TEST(buf, 30, "%*.*s", 5, 5, "abcd");
+    TEST(buf, 30, "%*.*s", -1, -1, "abcd");
+    TEST(buf, 30, "%*.*s", -5, -5, "abcd");
+
+    TEST(buf, 30, "%c", 'a');
+    TEST(buf, 30, "%1c", 'a');
+    TEST(buf, 30, "%2c", 'a');
+
+    TEST(buf, 30, "%.1c", 'a');
+    TEST(buf, 30, "%1.1c", 'a');
+    TEST(buf, 30, "%2.2c", 'a');
+
+    TEST(buf, 30, "%-.1c", 'a');
+    TEST(buf, 30, "%-1.1c", 'a');
+    TEST(buf, 30, "%-2.2c", 'a');
+
+    TEST_NONFATAL(buf, 30, "%-.-1c", 'a');
+    TEST_NONFATAL(buf, 30, "%-1.-1c", 'a');
+    TEST_NONFATAL(buf, 30, "%-2.-2c", 'a');
+
+    TEST(buf, 10, "%d", 0);
+    TEST(buf, 10, "%i", 0);
+    TEST(buf, 10, "%o", 0);
+    TEST(buf, 10, "%x", 0);
+    TEST(buf, 10, "%u", 0);
+    TEST(buf, 10, "%X", 0);
+
+    TEST(buf, 10, "%d", 0x1f);
+    TEST(buf, 10, "%i", 0x1f);
+    TEST(buf, 10, "%o", 0x1f);
+    TEST(buf, 10, "%x", 0x1f);
+    TEST(buf, 10, "%u", 0x1f);
+    TEST(buf, 10, "%X", 0x1f);
+
+    TEST(buf, 30, "%"PRIo8, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIo16, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIo32, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIo64, 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "%"PRId8, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRId16, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRId32, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRId64, 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "%"PRIi8, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIi16, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIi32, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIi64, 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "%"PRIu8, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIu16, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIu32, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIu64, 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "%"PRIx8, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIx16, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIx32, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIx64, 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "%"PRIX8, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIX16, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIX32, 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%"PRIX64, 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "%hho", 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%hhd", 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%hhi", 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%hhu", 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%hhx", 0xfedcba9876543210ULL);
+    TEST(buf, 30, "%hhX", 0xfedcba9876543210ULL);
+
+    TEST(buf, 30, "% d", 0);
+    TEST(buf, 30, "%-d", 0);
+    TEST(buf, 30, "%+d", 0);
+
+    TEST(buf, 30, "% d", 1);
+    TEST(buf, 30, "%-d", 1);
+    TEST(buf, 30, "%+d", 1);
+
+    TEST(buf, 30, "% d", -1);
+    TEST(buf, 30, "%-d", -1);
+    TEST(buf, 30, "%+d", -1);
+
+    TEST(buf, 30, "%"PRId64, INT64_MAX);
+    TEST(buf, 30, "%"PRId64, INT64_MIN);
+
+    TEST(buf, 30, "%"PRIo64, INT64_MAX);
+    TEST(buf, 30, "%"PRIu64, INT64_MAX);
+    TEST(buf, 30, "%"PRIx64, INT64_MAX);
+    TEST(buf, 30, "%"PRIX64, INT64_MAX);
+
+    TEST(buf, 30, "%d", 2);
+    TEST(buf, 30, "%2d", 2);
+    TEST(buf, 30, "%2.1d", 2);
+    TEST(buf, 30, "%2.3d", 2);
+
+    TEST(buf, 30, "% d", 2);
+    TEST(buf, 30, "% 2d", 2);
+    TEST(buf, 30, "% 2.1d", 2);
+    TEST(buf, 30, "% 2.3d", 2);
+
+    TEST(buf, 30, "%+d", 2);
+    TEST(buf, 30, "%+2d", 2);
+    TEST(buf, 30, "%+2.1d", 2);
+    TEST(buf, 30, "%+2.3d", 2);
+
+    TEST(buf, 30, "%-d", 2);
+    TEST(buf, 30, "%-2d", 2);
+    TEST(buf, 30, "%-2.1d", 2);
+    TEST(buf, 30, "%-2.3d", 2);
+
+    TEST(buf, 30, "%-d", -2);
+    TEST(buf, 30, "%-2d", -2);
+    TEST(buf, 30, "%-2.1d", -2);
+    TEST(buf, 30, "%-2.3d", -2);
+
+    TEST(buf, 30, "%#05x", 0xf);
+    TEST(buf, 30, "%# 05x", 0xf);
+    TEST(buf, 30, "%#-05x", 0xf);
+    TEST(buf, 30, "%#+05x", 0xf);
+    TEST(buf, 30, "%#- 5x", 0xf);
+    TEST(buf, 30, "%#+ 5x", 0xf);
+
+    TEST(buf, 30, "%#05.2x", 0xf);
+    TEST(buf, 30, "%# 05.2x", 0xf);
+    TEST(buf, 30, "%#-05.2x", 0xf);
+    TEST(buf, 30, "%#+05.2x", 0xf);
+    TEST(buf, 30, "%#- 5.2x", 0xf);
+    TEST(buf, 30, "%#+ 5.2x", 0xf);
+
+    TEST(buf, 30, "%#05.2x", 0xffff);
+    TEST(buf, 30, "%# 05.2x", 0xffff);
+    TEST(buf, 30, "%#-05.2x", 0xffff);
+    TEST(buf, 30, "%#+05.2x", 0xffff);
+    TEST(buf, 30, "%#- 5.2x", 0xffff);
+    TEST(buf, 30, "%#+ 5.2x", 0xffff);
+
+    TEST(buf, 30, "%#05.2o", 0xffff);
+    TEST(buf, 30, "%# 05.2o", 0xffff);
+    TEST(buf, 30, "%#-05.2o", 0xffff);
+    TEST(buf, 30, "%#+05.2o", 0xffff);
+    TEST(buf, 30, "%#- 5.2o", 0xffff);
+    TEST(buf, 30, "%#+ 5.2o", 0xffff);
+
+    TEST(buf, 30, "%#05.2d", 0xffff);
+    TEST(buf, 30, "%# 05.2d", 0xffff);
+    TEST(buf, 30, "%#-05.2d", 0xffff);
+    TEST(buf, 30, "%#+05.2d", 0xffff);
+    TEST(buf, 30, "%#- 5.2d", 0xffff);
+    TEST(buf, 30, "%#+ 5.2d", 0xffff);
+
+    TEST(buf, 30, "%#05.2zu", 0xffff);
+    TEST(buf, 30, "%# 05.2zu", 0xffff);
+    TEST(buf, 30, "%#-05.2zu", 0xffff);
+    TEST(buf, 30, "%#+05.2zu", 0xffff);
+    TEST(buf, 30, "%#- 5.2zu", 0xffff);
+    TEST(buf, 30, "%#+ 5.2zu", 0xffff);
+
+    TEST_NONFATAL(buf, 20, "%p", buf);
+    TEST_NONFATAL(buf, 20, "%2p", buf);
+    TEST(buf, 20, "%#2p", buf);
+    TEST(buf, 20, "%#2.0p", buf);
+    TEST(buf, 20, "%#2.20p", buf);
+    TEST_NONFATAL(buf, 20, "%2.20p", buf);
+    TEST(buf, 20, "%#20.20p", buf);
+    TEST_NONFATAL(buf, 20, "%20.20p", buf);
+    TEST_NONFATAL(buf, 20, "%03p", buf);
+
+    printf("\n%s\n", success ? "SUCCESS" : "FAILED");
+    return !success;
+}