text: /*                   T E S T _ S S C A N F . C
   * BRL-CAD
   *
   * Copyright (c) 2012-2022 United States Government as represented by
   * the U.S. Army Research Laboratory.
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public License
   * version 2.1 as published by the Free Software Foundation.
   *
   * This 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 this file; see the file named COPYING for more
   * information.
   */
  /** @file bu_sscanf.c
   *
   * Test bu_sscanf routine.
   *
   */
  
  #include "common.h"
  
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  
  #include "bu.h"
  #include "vmath.h"
  
  #define TS_FLOAT_TOL .0005
  
  #define TS_STR_SIZE 128
  #define TS_STR_WIDTH "127"
  
  /* scanf specification summarized from Linux man page:
   *
   * If processing of a directive fails, no further input is read, and
   * scanf returns.
   *
   * Returns number of input items successfully matched and assigned, or
   * EOF if a read error occurs (errno and stream error indicator are set),
   * EOI is encountered before the first successful conversion, or a
   * matching failure occurs.
   *
   * Can match 0 or more whitespace chars, ordinary character, or a
   * conversion specification:
   *
   * %[*][a][max_field_width][type_modifier]<conversion_specifier>
   *
   * '*' suppresses assignment. No pointer is needed, input is discarded,
   *     and conversion isn't added to the successful conversion count.
   *
   * 'a' in GNU, this represents an extension that automatically allocates
   *     a string large enough to hold a converted string and assigns the
   *     address to the supplied char*. In C99, a is synonymous with e/E/f/g.
   *
   * max_field_width - scan stops stops reading for a conversion at the
   *     first non-matching character or when max_field_width is reached
   *     (not including discarded leading whitespace or the '\0' appended
   *     to string conversions).
   *
   * modifiers:
   *  h - expect (signed|unsigned) short int (not int) pointer
   * hh - expect (signed|unsigned) char (not int) pointer
   *  l - expect (signed|unsigned) long int (not int) pointer, or double (not
   *      float), or wide char (not char) pointer
   *  L - expect long double (not double) pointer
   *
   * C99 modifiers:
   *  j - expect (signed|unsigned) intmax_t or uintmax_t (not int) pointer
   *  t - expect ptrdiff_t (not int) pointer
   *  z - expect size_t (not int) pointer
   *
   * Note: the "long long" type extension is specified with 'q' for 4.4BSD,
   *       and 'll' or 'L' for GNU.
   *
   * conversion specifiers:
   *  % - NOT A CONVERSION. Literal %.
   *  d - signed/unsigned decimal integer
   *  i - Signed/unsigned base 8/10/16 integer. Base 16 chose if 0x or 0X is
   *      read first, base 8 if 0 is read first, and base 10 otherwise. Only
   *      characters valid for the base are read.
   *  o - unsigned octal
   *  u - unsigned decimal
   *x/X - unsigned hexadecimal
   *a/e/E/f/g - signed/unsigned float (a is C99)
   *  s - Match a sequence of non-white-space characters. Pointer must be
   *      large enough for all characters plus the '\0' scanf appends.
   *  c - Match a sequence (1 by default) of characters. Pointer must be large
   *      enough for all characters. No '\0' is appended. Leading white space
   *      IS NOT discarded.
   *[...] - Like c, but only accept the specified character class. Leading white
   *        space IS NOT discarded. To include ']' in the set, it must appear
   *        first after '[' or '^'. To include '-' in the set, it must appear
   *        last before ']'.
   *  p - pointer (printf format)
   *  n - NOT A CONVERSION. Stores number of characters read so far in the int
   *      pointer. PROBABLY shouldn't affect returned conversion count.
   */
  
  /* Using these constants to identify pointer type instead of adding all the
   * logic needed to parse the format string.
   */
  enum {
      SCAN_SHORTSHORT, SCAN_USHORTSHORT,
      SCAN_SHORT, SCAN_USHORT,
      SCAN_INT, SCAN_UINT,
      SCAN_LONG, SCAN_ULONG,
      SCAN_POINTER,
      SCAN_FLOAT, SCAN_DOUBLE, SCAN_LDOUBLE,
      SCAN_SIZE,
      SCAN_PTRDIFF
  };
  
  static void
  print_src_and_fmt(const char *src, const char *fmt)
  {
      printf("\"%s\", \"%s\"\n", src, fmt);
  }
  
  /* This test is intended to catch errors in the tests themselves. If we make a
   * typo in a test format string causing fewer assignments than expected, then
   * we could end up silently comparing error behavior rather than the behavior
   * we actually want to test.
   */
  static void
  checkReturnVal(const char *funcStr, int actual, int expected)
  {
      if (actual != expected) {
  	bu_exit(1, "\tError: %s returned %d%s. Expected %d%s.\n", funcStr,
  		actual, (actual == EOF) ? " (EOF)" : "",
  		expected, (expected == EOF) ? " (EOF)" : "");
      }
  }
  
  /* Exit if returns from sscanf and bu_sscanf are not equal. */
  static void
  checkReturnsEqual(int ret, int bu_ret)
  {
      if (bu_ret != ret) {
  	bu_exit(1, "\t[FAIL] sscanf returned %d but bu_sscanf returned %d.\n",
  		ret, bu_ret);
      }
  }
  
  #define CHECK_INT_VALS_EQUAL(int_type, valp, bu_valp) \
      { \
  	int_type _val = *(int_type*)(valp); \
  	int_type _bu_val = *(int_type*)(bu_valp); \
  	if (_val != _bu_val) { \
  	    bu_exit(1, "\t[FAIL] conversion value mismatch.\n" \
  		    "\t(sscanf) %lld != %lld (bu_sscanf).\n", \
  		    (long long int)_val, (long long int)_bu_val); \
  	} \
      }
  
  #define CHECK_FLOAT_VALS_EQUAL(float_type, valp, bu_valp) \
      { \
  	float_type _val = *(float_type*)(valp); \
  	float_type _bu_val = *(float_type*)(bu_valp); \
  	if (!NEAR_EQUAL(_val, _bu_val, TS_FLOAT_TOL)) { \
  	    bu_exit(1, "\t[FAIL] conversion value mismatch.\n" \
  		    "\t(sscanf) %Le != %Le (bu_sscanf).\n", \
  		    (long double)_val, (long double)_bu_val); \
  	} \
      }
  
  static void
  test_sscanf(int type, const char *src, const char *fmt) {
      int ret = 0;
      int bu_ret = 0;
      void *val = NULL;
      void *bu_val = NULL;
  
      print_src_and_fmt(src, fmt);
  
      /* call sscanf and bu_sscanf with appropriately cast pointers */
  #define SSCANF_TYPE(type) \
      BU_ALLOC(val, type); \
      BU_ALLOC(bu_val, type); \
  \
      ret = sscanf(src, fmt, (type*)val); \
      bu_ret = bu_sscanf(src, fmt, (type*)bu_val); \
  \
      checkReturnVal("sscanf", ret, 1); \
      checkReturnsEqual(bu_ret, ret);
  
      switch (type) {
  	case SCAN_INT:
  	    SSCANF_TYPE(int);
  	    CHECK_INT_VALS_EQUAL(int, val, bu_val);
  	    break;
  	case SCAN_UINT:
  	    SSCANF_TYPE(unsigned);
  	    CHECK_INT_VALS_EQUAL(unsigned, val, bu_val);
  	    break;
  	case SCAN_SHORT:
  	    SSCANF_TYPE(short);
  	    CHECK_INT_VALS_EQUAL(short, val, bu_val);
  	    break;
  	case SCAN_USHORT:
  	    SSCANF_TYPE(unsigned short);
  	    CHECK_INT_VALS_EQUAL(unsigned short, val, bu_val);
  	    break;
  	case SCAN_SHORTSHORT:
  	    SSCANF_TYPE(char);
  	    CHECK_INT_VALS_EQUAL(char, val, bu_val);
  	    break;
  	case SCAN_USHORTSHORT:
  	    SSCANF_TYPE(unsigned char);
  	    CHECK_INT_VALS_EQUAL(unsigned char, val, bu_val);
  	    break;
  	case SCAN_LONG:
  	    SSCANF_TYPE(long);
  	    CHECK_INT_VALS_EQUAL(long, val, bu_val);
  	    break;
  	case SCAN_ULONG:
  	    SSCANF_TYPE(unsigned long);
  	    CHECK_INT_VALS_EQUAL(unsigned long, val, bu_val);
  	    break;
  	case SCAN_SIZE:
  	    SSCANF_TYPE(size_t);
  	    CHECK_INT_VALS_EQUAL(size_t, val, bu_val);
  	    break;
  	case SCAN_PTRDIFF:
  	    SSCANF_TYPE(ptrdiff_t);
  	    CHECK_INT_VALS_EQUAL(ptrdiff_t, val, bu_val);
  	    break;
  	case SCAN_POINTER:
  	    ret = sscanf(src, fmt, &val);
  	    bu_ret = bu_sscanf(src, fmt, &bu_val);
  
  	    checkReturnVal("sscanf", ret, 1);
  	    checkReturnsEqual(bu_ret, ret);
  
  	    if (val != bu_val) {
  		bu_exit(1, "\t[FAIL] conversion value mismatch.\n"
  			"\t(sscanf) %p != %p (bu_sscanf).\n",
  			val, bu_val);
  	    }
  	    val = bu_val = NULL;
  	    break;
  	case SCAN_FLOAT:
  	    SSCANF_TYPE(float);
  	    CHECK_FLOAT_VALS_EQUAL(float, val, bu_val);
  	    break;
  	case SCAN_DOUBLE:
  	    SSCANF_TYPE(double);
  	    CHECK_FLOAT_VALS_EQUAL(double, val, bu_val);
  	    break;
  	case SCAN_LDOUBLE:
  	    SSCANF_TYPE(long double);
  	    CHECK_FLOAT_VALS_EQUAL(long double, val, bu_val);
  	    break;
  	default:
  	    bu_exit(1, "Error: test_sscanf was given an unrecognized pointer type.\n");
      }
  
      bu_free(val, "test_sscanf val");
      if (val != NULL) {
  	val = NULL;
      }
      bu_free(bu_val, "test_sscanf bu_val");
      if (bu_val != NULL) {
  	bu_val = NULL;
      }
  } /* test_sscanf */
  
  /* Here we define printable constants that should be safe on any platform.
   *
   * Note that we don't use the macros in limits.h or float.h, because they
   * aren't necessarily printable, as in:
   *     #define INT_MIN (-INT_MAX - 1)
   */
  #define   SIGNED_HH_DEC 127
  #define UNSIGNED_HH_DEC 255
  #define   SIGNED_HH_OCT 0177
  #define UNSIGNED_HH_OCT 0377
  #define   SIGNED_HH_HEX 0x7F
  #define UNSIGNED_HH_HEX 0xFF
  
  #define   SIGNED_DEC 32767
  #define UNSIGNED_DEC 65535
  #define   SIGNED_OCT 0077777
  #define UNSIGNED_OCT 0177777
  #define   SIGNED_HEX 0x7FFF
  #define UNSIGNED_HEX 0xFFFF
  
  #define   SIGNED_L_DEC 2147483647
  #define UNSIGNED_L_DEC 4294967295
  #define   SIGNED_L_OCT 17777777777
  #define UNSIGNED_L_OCT 37777777777
  #define   SIGNED_L_HEX 0x7FFFFFFF
  #define UNSIGNED_L_HEX 0xFFFFFFFF
  
  #define SMALL_FLT 1.0e-37
  #define LARGE_FLT 1.0e+37
  
  static void
  doNumericTests(void)
  {
  #define TEST_SIGNED_CONSTANT(str, fmt) \
      test_sscanf(SCAN_SHORTSHORT, str, "%hh" CPP_STR(fmt)); \
      test_sscanf(SCAN_SIZE, str, "%z" CPP_STR(fmt)); \
      test_sscanf(SCAN_SHORT, str, "%h" CPP_STR(fmt)); \
      test_sscanf(SCAN_INT, str, "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_LONG, str, "%l" CPP_STR(fmt));
  
      TEST_SIGNED_CONSTANT("0", d);
      TEST_SIGNED_CONSTANT("0", i);
      TEST_SIGNED_CONSTANT("000", i);
      TEST_SIGNED_CONSTANT("0x0", i);
      TEST_SIGNED_CONSTANT("0X0", i);
  
  #define TEST_UNSIGNED_CONSTANT(str, fmt) \
      test_sscanf(SCAN_USHORTSHORT, str, "%hh" CPP_STR(fmt)); \
      test_sscanf(SCAN_SIZE, str, "%z" CPP_STR(fmt)); \
      test_sscanf(SCAN_USHORT, str, "%h" CPP_STR(fmt)); \
      test_sscanf(SCAN_UINT, str, "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_ULONG, str, "%l" CPP_STR(fmt));
  
      TEST_UNSIGNED_CONSTANT("0", u);
      TEST_UNSIGNED_CONSTANT("0", o);
      TEST_UNSIGNED_CONSTANT("000", o);
      TEST_UNSIGNED_CONSTANT("0x0", x);
      TEST_UNSIGNED_CONSTANT("0x0", X);
      TEST_UNSIGNED_CONSTANT("0X0", x);
      TEST_UNSIGNED_CONSTANT("0X0", X);
  
  #define TEST_SIGNED_CONSTANTS(small, med, large, fmt) \
      test_sscanf(SCAN_SHORTSHORT, "+" CPP_STR(small), "%hh" CPP_STR(fmt)); \
      test_sscanf(SCAN_SHORTSHORT, CPP_STR(small), "%hh" CPP_STR(fmt)); \
      test_sscanf(SCAN_SHORTSHORT, "-" CPP_STR(small), "%hh" CPP_STR(fmt)); \
      test_sscanf(SCAN_SIZE, CPP_STR(small), "%z" CPP_STR(fmt)); \
      test_sscanf(SCAN_SHORT, "+" CPP_STR(med), "%h" CPP_STR(fmt)); \
      test_sscanf(SCAN_SHORT, CPP_STR(med), "%h" CPP_STR(fmt)); \
      test_sscanf(SCAN_SHORT, "-" CPP_STR(med), "%h" CPP_STR(fmt)); \
      test_sscanf(SCAN_INT, "+" CPP_STR(med), "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_INT, CPP_STR(med), "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_INT, "-" CPP_STR(med), "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_LONG, "+" CPP_STR(large), "%l" CPP_STR(fmt)); \
      test_sscanf(SCAN_LONG, CPP_STR(large), "%l" CPP_STR(fmt)); \
      test_sscanf(SCAN_LONG, "-" CPP_STR(large), "%l" CPP_STR(fmt));
  
      TEST_SIGNED_CONSTANTS(SIGNED_HH_DEC, SIGNED_DEC, SIGNED_L_DEC, d);
      TEST_SIGNED_CONSTANTS(SIGNED_HH_DEC, SIGNED_DEC, SIGNED_L_DEC, i);
      TEST_SIGNED_CONSTANTS(SIGNED_HH_OCT, SIGNED_OCT, SIGNED_L_OCT, i);
      TEST_SIGNED_CONSTANTS(SIGNED_HH_HEX, SIGNED_HEX, SIGNED_L_HEX, i);
  
  #define TEST_UNSIGNED_CONSTANTS(small, med, large, fmt) \
      test_sscanf(SCAN_USHORTSHORT, CPP_STR(small), "%hh" CPP_STR(fmt)); \
      test_sscanf(SCAN_SIZE, CPP_STR(small), "%z" CPP_STR(fmt)); \
      test_sscanf(SCAN_USHORT, CPP_STR(med), "%h" CPP_STR(fmt)); \
      test_sscanf(SCAN_UINT, CPP_STR(med), "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_ULONG, CPP_STR(large), "%l" CPP_STR(fmt));
  
      TEST_UNSIGNED_CONSTANTS(UNSIGNED_HH_DEC, UNSIGNED_DEC, UNSIGNED_L_DEC, u);
      TEST_UNSIGNED_CONSTANTS(UNSIGNED_HH_OCT, UNSIGNED_OCT, UNSIGNED_L_OCT, o);
      TEST_UNSIGNED_CONSTANTS(UNSIGNED_HH_HEX, UNSIGNED_HEX, UNSIGNED_L_HEX, x);
      TEST_UNSIGNED_CONSTANTS(UNSIGNED_HH_HEX, UNSIGNED_HEX, UNSIGNED_L_HEX, X);
  
  #define TEST_FLOAT_CONSTANT(c, fmt) \
      test_sscanf(SCAN_FLOAT, c, "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_DOUBLE, c, "%l" CPP_STR(fmt)); \
      test_sscanf(SCAN_LDOUBLE, c, "%L" CPP_STR(fmt));
  
  #define TEST_FLOAT_VARIANT(fmt) \
      TEST_FLOAT_CONSTANT("0.0", fmt); \
      TEST_FLOAT_CONSTANT("0.0e0", fmt); \
      TEST_FLOAT_CONSTANT("0.0e1", fmt); \
      test_sscanf(SCAN_FLOAT, CPP_XSTR(SMALL_FLT), "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_FLOAT, CPP_XSTR(LARGE_FLT), "%" CPP_STR(fmt)); \
      test_sscanf(SCAN_DOUBLE, CPP_XSTR(SMALL_FLT), "%l" CPP_STR(fmt)); \
      test_sscanf(SCAN_DOUBLE, CPP_XSTR(LARGE_FLT), "%l" CPP_STR(fmt)); \
      test_sscanf(SCAN_LDOUBLE, CPP_XSTR(SMALL_FLT), "%L" CPP_STR(fmt)); \
      test_sscanf(SCAN_LDOUBLE, CPP_XSTR(LARGE_FLT), "%L" CPP_STR(fmt));
  
      TEST_FLOAT_VARIANT(a);
      TEST_FLOAT_VARIANT(e);
      TEST_FLOAT_VARIANT(f);
      TEST_FLOAT_VARIANT(g);
  
      TEST_FLOAT_VARIANT(A);
      TEST_FLOAT_VARIANT(E);
      TEST_FLOAT_VARIANT(F);
      TEST_FLOAT_VARIANT(G);
  }
  
  /* string test routine */
  static void
  test_sscanf_s(const char *src, const char *fmt)
  {
      int ret = 0;
      int bu_ret = 0;
      char dest[TS_STR_SIZE], bu_dest[TS_STR_SIZE];
  
      /* ensure NULL termination even for c and [...] */
      memset(dest, '\0', TS_STR_SIZE);
      memset(bu_dest, '\0', TS_STR_SIZE);
  
      print_src_and_fmt(src, fmt);
  
      ret = sscanf(src, fmt, dest);
      bu_ret = bu_sscanf(src, fmt, bu_dest);
  
      checkReturnVal("sscanf", ret, 1);
      checkReturnsEqual(bu_ret, ret);
  
      if (!BU_STR_EQUAL(dest, bu_dest)) {
  	bu_exit(1, "\t[FAIL] conversion value mismatch.\n"
  		"\t(sscanf) %s != %s (bu_sscanf).\n",
  		dest, bu_dest);
      }
  }
  
  static void
  doStringTests(void)
  {
      int bu_ret;
      char buf[TS_STR_SIZE];
      char *cp;
  
  #define TEST_STR_SPACE " \t\naBc\n"
  #define TEST_STR_NOSPACE "aBc"
  
  #define TEST_TERMINATION(src, fmt) \
      print_src_and_fmt(src, fmt); \
      /* init so that 'X' appears after the last char written by bu_sscanf */ \
      memset(buf, 'X', TS_STR_SIZE); \
      bu_ret = bu_sscanf(src, fmt, buf); \
      checkReturnVal("bu_sscanf", bu_ret, 1); \
      cp = strchr(buf, 'X');
  
      /* %s should append '\0' */
      TEST_TERMINATION(TEST_STR_SPACE, "%" TS_STR_WIDTH "s");
      if (cp != NULL) {
  	/* cp != NULL implies strchr found 'X' before finding '\0' */
  	bu_exit(1, "\t[FAIL] bu_sscanf didn't null-terminate %%s conversion.\n");
      }
  
      /* %[...] should append '\0' */
      TEST_TERMINATION(TEST_STR_SPACE, "%" TS_STR_WIDTH "[^z]");
      if (cp != NULL) {
  	/* cp != NULL implies strchr found 'X' before finding '\0' */
  	bu_exit(1, "\t[FAIL] bu_sscanf null-terminated %%[...] conversion.\n");
      }
  
      /* %c should NOT append '\0' */
      TEST_TERMINATION(TEST_STR_SPACE, "%" TS_STR_WIDTH "c");
      if (cp == NULL) {
  	/* cp == NULL implies strchr found '\0' before finding 'X' */
  	bu_exit(1, "\t[FAIL] bu_sscanf null-terminated %%c conversion.\n");
      }
  
      /* For %s sscanf should not include leading whitespace in the string, so
       * the following should all be equivalent.
       */
      test_sscanf_s(TEST_STR_NOSPACE, "%" TS_STR_WIDTH "s");
      test_sscanf_s(TEST_STR_NOSPACE, " %" TS_STR_WIDTH "s");
      test_sscanf_s(TEST_STR_SPACE, "%" TS_STR_WIDTH "s");
      test_sscanf_s(TEST_STR_SPACE, " %" TS_STR_WIDTH "s");
  
      /* For %c, leading whitespace should be included unless the conversion
       * specifier is preceded by whitespace.
       */
      test_sscanf_s(TEST_STR_SPACE, "%c");  /* should assign ' ' */
      test_sscanf_s(TEST_STR_SPACE, " %c"); /* should assign 'a' */
  
      /* For %[...], should act like %s, but should assign any/only characters
       * in the class.
       */
      test_sscanf_s(TEST_STR_SPACE, "%" TS_STR_WIDTH "[^A-Z]");  /* should assign " \t\na" */
      test_sscanf_s(TEST_STR_SPACE, " %" TS_STR_WIDTH "[^A-Z]"); /* should assign "a"      */
      test_sscanf_s(TEST_STR_SPACE, " %" TS_STR_WIDTH "[a-z]");  /* should assign "a"      */
  
      /* should be able to include literal ] and - characters */
      test_sscanf_s("[-[- ", "%" TS_STR_WIDTH "[[-]");
      test_sscanf_s(" zZ [- ", "%" TS_STR_WIDTH "[^[-]");
      test_sscanf_s(" zZ -[ ", "%" TS_STR_WIDTH "[^[-]");
  }
  
  static void
  doPointerTests(void)
  {
      test_sscanf(SCAN_POINTER, "0", "%p");
      test_sscanf(SCAN_POINTER, CPP_XSTR(UNSIGNED_L_HEX), "%p");
  
      test_sscanf(SCAN_PTRDIFF, "0", "%ti");
      test_sscanf(SCAN_PTRDIFF, "0", "%td");
      test_sscanf(SCAN_PTRDIFF, "0", "%tu");
      test_sscanf(SCAN_PTRDIFF, CPP_XSTR(SIGNED_L_DEC), "%ti");
      test_sscanf(SCAN_PTRDIFF, CPP_XSTR(SIGNED_L_DEC), "%td");
      test_sscanf(SCAN_PTRDIFF, CPP_XSTR(UNSIGNED_L_DEC), "%tu");
  
      test_sscanf(SCAN_PTRDIFF, "000", "%ti");
      test_sscanf(SCAN_PTRDIFF, "000", "%to");
      //TODO: Test appears to break with newer C/C++ standards
      //test_sscanf(SCAN_PTRDIFF, CPP_XSTR(SIGNED_L_OCT), "%ti");
      test_sscanf(SCAN_PTRDIFF, CPP_XSTR(UNSIGNED_L_OCT), "%to");
  
      test_sscanf(SCAN_PTRDIFF, "0x0", "%ti");
      test_sscanf(SCAN_PTRDIFF, "0x0", "%tx");
      test_sscanf(SCAN_PTRDIFF, CPP_XSTR(SIGNED_L_HEX), "%ti");
      test_sscanf(SCAN_PTRDIFF, CPP_XSTR(UNSIGNED_L_HEX), "%tx");
  }
  
  static void
  test_sscanf_noconv(const char *src, const char *fmt)
  {
      int count, bu_count, ret, bu_ret;
  
      count = bu_count = 0;
  
      print_src_and_fmt(src, fmt);
  
      ret = sscanf(src, fmt, &count);
      bu_ret = bu_sscanf(src, fmt, &bu_count);
  
      checkReturnVal("sscanf", ret, 0);
      checkReturnsEqual(bu_ret, ret);
  
      if (bu_count != count) {
  	bu_exit(1, "\t[FAIL] sscanf consumed %d chars, "
  		"but bu_sscanf consumed %d.\n", count, bu_count);
      }
  }
  
  static void
  doNonConversionTests(void)
  {
      /* %n - don't convert/assign, but do store consumed char count */
      test_sscanf_noconv(". \tg\t\tn i    RTSA si sihT", ". g n i RTSA%n");
      test_sscanf_noconv(" foo", "foo%n");
  
      /* %% - don't convert/assign, but do scan literal % */
      test_sscanf_noconv("%%n    %", "%%%%n %%%n");
  
      /* suppressed assignments */
      test_sscanf_noconv(CPP_XSTR(SIGNED_DEC), "%*d%n");
      test_sscanf_noconv(CPP_XSTR(UNSIGNED_DEC), "%*u%n");
      test_sscanf_noconv(CPP_XSTR(LARGE_FLT), "%*f%n");
      test_sscanf_noconv("42 42  4.2e1", "%*d %*u %*f%n");
  }
  
  #define TS_NUM_ASSIGNMENTS 3
  
  static void
  test_string_width(const char *src, const char *fmt)
  {
      int i, j, ret, bu_ret;
      char str_vals[TS_NUM_ASSIGNMENTS][TS_STR_SIZE];
      char bu_str_vals[TS_NUM_ASSIGNMENTS][TS_STR_SIZE];
  
      print_src_and_fmt(src, fmt);
  
      /* init so that 'X' appears after the last char written by bu_sscanf */
      for (i = 0; i < TS_NUM_ASSIGNMENTS; ++i) {
  	memset(str_vals[i], 'X', TS_STR_SIZE);
  	memset(bu_str_vals[i], 'X', TS_STR_SIZE);
      }
  
      ret = sscanf(src, fmt, str_vals[0], str_vals[1], str_vals[2]);
      bu_ret = bu_sscanf(src, fmt, bu_str_vals[0], bu_str_vals[1],
  		       bu_str_vals[2]);
      checkReturnVal("sscanf", ret, 3);
      checkReturnsEqual(bu_ret, ret);
  
      /* each str should be exactly equivalent to each bu_str */
      for (i = 0; i < TS_NUM_ASSIGNMENTS; ++i) {
  	for (j = 0; j < TS_STR_SIZE - 1; ++j) {
  	    if (str_vals[i][j] != bu_str_vals[i][j]) {
  
  		/* terminate for printing */
  		str_vals[i][j + 1] = '\0';
  		bu_str_vals[i][j + 1] = '\0';
  
  		bu_exit(1, "\t[FAIL] conversion value mismatch.\n"
  			"\t(sscanf) %s != %s (bu_sscanf)\n",
  			str_vals[i], bu_str_vals[i]);
  	    }
  	}
      }
  }
  
  static void
  doWidthTests(void)
  {
      int i;
      int ret, bu_ret;
      void *val, *bu_val, *vals, *bu_vals;
  
  #define SCAN_3_VALS(type, src, fmt) \
      print_src_and_fmt(src, fmt); \
      vals = bu_malloc(sizeof(type) * TS_NUM_ASSIGNMENTS, "test_sscanf vals"); \
      bu_vals = bu_malloc(sizeof(type) * TS_NUM_ASSIGNMENTS, "test_sscanf bu_vals"); \
      ret = sscanf(src, fmt, &((type*)vals)[0], &((type*)vals)[1], &((type*)vals)[2]); \
      bu_ret = bu_sscanf(src, fmt, &((type*)bu_vals)[0], &((type*)bu_vals)[1], &((type*)bu_vals)[2]); \
      checkReturnVal("sscanf", ret, 3); \
      checkReturnsEqual(bu_ret, ret);
  
  #define TEST_INT_WIDTH(type, src, fmt) \
      SCAN_3_VALS(type, src, fmt); \
      for (i = 0; i < TS_NUM_ASSIGNMENTS; ++i) { \
  	val = (void*)&((type*)vals)[i]; \
  	bu_val = (void*)&((type*)bu_vals)[i]; \
  	CHECK_INT_VALS_EQUAL(type, val, bu_val); \
      } \
      bu_free(vals, "test_sscanf vals"); \
      bu_free(bu_vals, "test_sscanf bu_vals");
  
  #define TEST_FLOAT_WIDTH(type, src, fmt) \
      SCAN_3_VALS(type, src, fmt); \
      for (i = 0; i < TS_NUM_ASSIGNMENTS; ++i) { \
  	val = (void*)&((type*)vals)[i]; \
  	bu_val = (void*)&((type*)bu_vals)[i]; \
  	CHECK_FLOAT_VALS_EQUAL(type, val, bu_val); \
      } \
      bu_free(vals, "test_sscanf vals"); \
      bu_free(bu_vals, "test_sscanf bu_vals");
  
      /* Stop at non-matching even if width not met. */
      TEST_INT_WIDTH(int, "12 34 5a6", "%5d %5d %5d");
      TEST_INT_WIDTH(int, "12 0042 0x3z8", "%5i %5i %5i");
      TEST_INT_WIDTH(unsigned, "0xC 0x22 0x38", "%5x %5x %5x");
      TEST_FLOAT_WIDTH(float, ".0012 .34 56.0a0", "%10f %10f %10f");
      TEST_FLOAT_WIDTH(double, ".0012 .34 56.0a0", "%10lf %10lf %10lf");
      test_string_width("aa AA aa", "%5s %5s %5s");
      test_string_width("1234512345123451", "%5c %5c %5c");
      test_string_width("aaAA  zzzzzz", "%5[a]%5[A] %5[z]");
  
      /* Stop at width even if there are more matching chars.
       * Do not include discarded whitespace in count.
       */
      TEST_INT_WIDTH(int, "  123\t456", " %1d%2d %3d");
      TEST_INT_WIDTH(int, "  10\t0x38", " %1i%1i %4i");
      TEST_INT_WIDTH(unsigned, "  0xC00X22\t0x38", " %4x%3x %3x");
      TEST_FLOAT_WIDTH(float, "  .0012\t.3456", " %3f%2f %4f");
      TEST_FLOAT_WIDTH(double, "  .0012\t.3456", " %3lf%2lf %4lf");
      test_string_width("  abc  ABCDE", " %2s%1s  %4s");
      test_string_width("abc  ABCD", "%2c%2c  %4c");
      test_string_width("aaAA   1%1%1%", "%2[aA]%3[A ] %5[1%]");
  }
  
  static void
  doErrorTests(void)
  {
      int i, ret, bu_ret;
  
  #define FMT_ASSIGN3(fmt) \
      "%" CPP_STR(fmt) " %" CPP_STR(fmt) " %" CPP_STR(fmt)
  
  #define FMT_READ2_ASSIGN1(fmt) \
      "%*" CPP_STR(fmt) " %*" CPP_STR(fmt) " %" CPP_STR(fmt)
  
      /* Attempt to assign 3 values from src.
       * If src begins with an invalid input value, should return 0 to indicate
       * a matching failure.
       * If src is empty, should return EOF to indicate input failure.
       */
  #define TEST_FAILURE_1(type, type_fmt, type_init, src, expected_err) \
      { \
  	type vals[3] = { type_init, type_init, type_init }; \
  	print_src_and_fmt(src, FMT_ASSIGN3(type_fmt)); \
  	ret = sscanf(src, FMT_ASSIGN3(type_fmt), &vals[0], &vals[1], &vals[2]); \
  	bu_ret = sscanf(src, FMT_ASSIGN3(type_fmt), &vals[0], &vals[1], &vals[2]); \
  	checkReturnVal("sscanf", ret, expected_err); \
  	checkReturnsEqual(ret, bu_ret); \
  	for (i = 0; i < 3; ++i) { \
  	    if (vals[i] != type_init) { \
  		bu_exit(1, "\t[FAIL] No assignment expected, but vals[%d] " \
  			"changed from %" CPP_STR(type_fmt) " to %" CPP_STR(type_fmt) ".\n", \
  			i, type_init, vals[i]); \
  	    } \
  	} \
      }
  
      /* Attempt to read 2 values and assign 1 value from src.
       * If src includes 2 valid and 1 invalid input value, should return 0 to
       * indicate matching failure.
       * If src includes 2 valid values and terminates, should return EOF to
       * indicate input failure.
       */
  #define TEST_FAILURE_2(type, type_fmt, type_init, src, expected_err) \
      { \
  	type val = type_init; \
  	print_src_and_fmt(src, FMT_READ2_ASSIGN1(type_fmt)); \
  	ret = sscanf(src, FMT_READ2_ASSIGN1(type_fmt), &val); \
  	bu_ret = sscanf(src, FMT_READ2_ASSIGN1(type_fmt), &val); \
  	checkReturnVal("sscanf", ret, expected_err); \
  	checkReturnsEqual(ret, bu_ret); \
  	if (val != type_init) { \
  	    bu_exit(1, "\t[FAIL] No assignment expected, but val " \
  		    "changed from %" CPP_STR(type_fmt) " to %" CPP_STR(type_fmt) ".\n", \
  		    type_init, val); \
  	} \
      }
  
  #define EXPECT_MATCH_FAILURE 0
  #define EXPECT_INPUT_FAILURE EOF
  
  TEST_FAILURE_1(int, d, 0, "xx 34 56", EXPECT_MATCH_FAILURE);
  TEST_FAILURE_2(int, d, 0, "12 34 xx", EXPECT_MATCH_FAILURE);
  TEST_FAILURE_2(int, d, 0, "12 34", EXPECT_INPUT_FAILURE);
  TEST_FAILURE_1(int, d, 0, "", EXPECT_INPUT_FAILURE);
  
  // TODO - what is the intent here?  The 1[123] input seems to be causing problems
  #if 0
  TEST_FAILURE_1(char, 1[123], 'a', "x 2 3", EXPECT_MATCH_FAILURE);
  TEST_FAILURE_2(char, 1[123], 'a', "1 2 x", EXPECT_MATCH_FAILURE);
  TEST_FAILURE_2(char, 1[123], 'a', "1 2", EXPECT_INPUT_FAILURE);
  TEST_FAILURE_1(char, 1[123], 'a', "", EXPECT_INPUT_FAILURE);
  #endif
  }
  
  int
  main(int argc, char *argv[])
  {
      if (argc > 1) {
  	printf("Warning: %s takes no arguments.\n", argv[0]);
      }
  
      bu_setprogname(argv[0]);
  
      printf("bu_sscanf: running tests\n");
  
      doNumericTests();
      doStringTests();
      doPointerTests();
      doNonConversionTests();
      doWidthTests();
      doErrorTests();
  
      printf("bu_sscanf: testing complete\n");
  
      return 0;
  }
  
  /*
   * Local Variables:
   * mode: C
   * tab-width: 8
   * indent-tabs-mode: t
   * c-file-style: "stroustrup"
   * End:
   * ex: shiftwidth=4 tabstop=8
   */
  