gnucash maint: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Mon Jul 16 17:08:39 EDT 2018


Updated	 via  https://github.com/Gnucash/gnucash/commit/fa1b4c68 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6f1c63db (commit)
	 via  https://github.com/Gnucash/gnucash/commit/294e113f (commit)
	 via  https://github.com/Gnucash/gnucash/commit/414ab99a (commit)
	 via  https://github.com/Gnucash/gnucash/commit/b8ce2b54 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/aa4da810 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/dfe1f345 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/694d0f06 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/57c6f175 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e2907844 (commit)
	from  https://github.com/Gnucash/gnucash/commit/c444729d (commit)



commit fa1b4c685f53c6da1090a9e0a6dee3ff6790d3b7
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Jul 16 14:08:24 2018 -0700

    Add jenny to sources for combinatorics testing.

diff --git a/borrowed/jenny/jenny.c b/borrowed/jenny/jenny.c
new file mode 100644
index 0000000..721b021
--- /dev/null
+++ b/borrowed/jenny/jenny.c
@@ -0,0 +1,1806 @@
+/*
+-------------------------------------------------------------------------------
+By Bob Jenkins, March 2003.  Public domain.
+
+jenny.c -- jennyrate tests from m dimensions of features that cover all
+  n-tuples of features, n <= m, with each feature chosen from a different 
+  dimension.  For example, given 10 dimensions (m=10) with 2 to 8 features
+  apiece, cover all feature triplets (n=3).  A lower bound on the number
+  of tests required is the product of the sizes of the largest n dimensions.
+  Already-written tests can be piped in to be reused.
+
+Arguments
+  Arguments without a leading '-' : an integer in 2..52.  Represents a
+       dimension.  Dimensions are implicitly numbered 1..65535, in the 
+       order they appear.  Features in dimensions are always implicitly
+       given 1-character names, which are in order a..z, A..Z .  It's a
+       good idea to pass the output of jenny through a postprocessor that
+       expands these names into something intelligible.
+
+  -o : old.  -ofoo.txt reads existing tests from the file foo.txt, includes
+       those tests in the output, and adds whatever other tests are needed to
+       complete coverage.  An error is reported if the input tests are of the
+       wrong shape or contain disallowed feature interactions.  If you have
+       added new dimensions since those tests were written, be sure to include
+       a do-nothing feature in each new dimension, then pad the existing tests
+       with do-nothing out to the correct number of dimensions.
+
+  -h : help.  Print out instructions for using jenny.
+
+  -n : an integer.  Cover all n-tuples of features, one from each dimension.
+       Default is 2 (pairs).  3 (triplets) may be reasonable.  4 (quadruplets)
+       is definitely overkill.  n > 4 is highly discouraged.
+
+  -s : seed.  An integer.  Seed the random number generator.
+
+  -w : without this combination of features.  A feature is given by a dimension
+       number followed by a one-character feature name.  A single -w can
+       disallow multiple features in a dimension.  For example, -w1a2cd4ac
+       disallows the combinations (1a,2c,4a),(1a,2c,4c),(1a,2d,4a),(1a,2d,4c)
+       where 1a represents the first dimension's first feature, 2c is the 
+       second dimension's third feature, and 4a is the fourth dimension's
+       first feature.
+
+Example: 10 dimensions of 2, 3, 8, 3, 2, 2, 5, 3, 2, 2 features apiece,
+with some restrictions, asking for all triplets of features to be covered.
+This will produce at least 8*5*3=120 tests.  Splitting the 8 features in the
+third dimension into three dimensions each of length 2 would reduce the
+number of testcases required to at least 5*3*3=45.
+
+  jenny -n3 2 3 8 -w1a2bc3b -w1b3a 3 -w1a4b 2 2 5 3 2 2 -w9a10b -w3a4b -s3
+-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+-------------------------------------------------------------------------------
+Implementation:
+
+Internally, there can be 64K dimensions with 64K features apiece.  Externally,
+the number of features per dimensions is limited to just 52, and are implicitly
+named a..z, A..Z.  Other printable characters, like |, caused trouble in the
+shell when I tried to give them during a without.
+
+The program first finds tests for all features, then adds tests to cover
+all pairs of features, then all triples of features, and so forth up to
+the tuples size the user asked for.
+-------------------------------------------------------------------------------
+*/
+
+/*
+-------------------------------------------------------------------------------
+Structures
+-------------------------------------------------------------------------------
+*/
+
+typedef  unsigned char      ub1;
+typedef           char      sb1;
+typedef  unsigned short     ub2;
+typedef  signed   short     sb2;
+typedef  unsigned long      ub4;
+typedef  signed   long      sb4;
+typedef  unsigned long long ub8;
+typedef  signed   long long sb8;
+#define TRUE  1
+#define FALSE 0
+#define UB4MAXVAL 0xffffffff
+#define UB2MAXVAL 0xffff
+
+/*
+-------------------------------------------------------------------------------
+Random number stuff
+-------------------------------------------------------------------------------
+*/
+
+#define FLEARAND_SIZE 256
+typedef  struct flearandctx {
+  ub4 b,c,d,z;                                             /* special memory */
+  ub4 m[FLEARAND_SIZE];                         /* big random pool of memory */
+  ub4 r[FLEARAND_SIZE];                                           /* results */
+  ub4 q;                     /* counter, which result of r was last reported */
+} flearandctx;
+
+/* Pseudorandom numbers, courtesy of FLEA */
+void flearand_batch( flearandctx *x) {
+  ub4 a, b=x->b, c=x->c+(++x->z), d=x->d, i, *m=x->m, *r=x->r;
+  for (i=0; i<FLEARAND_SIZE; ++i) {
+    a = m[b % FLEARAND_SIZE];
+    m[b % FLEARAND_SIZE] = d;
+    d = (c<<19) + (c>>13) + b;
+    c = b ^ m[i];
+    b = a + d;
+    r[i] = c;
+  }
+  x->b=b; x->c=c; x->d=d;
+}
+
+ub4 flearand( flearandctx *x) {
+  if (!x->q--) {
+    x->q = FLEARAND_SIZE-1;
+    flearand_batch(x);
+  }
+  return x->r[x->q];
+}
+
+void flearand_init( flearandctx *x, ub4 seed) {
+  ub4    i;
+
+  x->b = x->c = x->d = x->z = seed;
+  for (i = 0; i<FLEARAND_SIZE; ++i) {
+    x->m[i] = seed;
+  }
+  for (i=0; i<10; ++i) {
+    flearand_batch(x);
+  }
+  x->q = 0;
+}
+
+
+
+
+/*
+------------------------------------------------------------------------------
+Other helper routines
+------------------------------------------------------------------------------
+*/
+
+#define TUPLE_ARRAY  5040       /* tuple array size, multiple of 1,2,3,4,5,6 */
+
+/* An arbitrary feature, prefix fe */
+typedef  struct feature {
+  ub2    d;                                                /* Dimension name */
+  ub2    f;                                                  /* Feature name */
+} feature;
+
+/* a tuple array, prefix tu */
+typedef  struct tu_arr {
+  struct tu_arr  *next;                                  /* next tuple array */
+  ub2             len;                               /* length of this array */
+  feature         fe[TUPLE_ARRAY];                        /* array of tuples */
+} tu_arr;
+
+/* an iterator over a tuple array, prefix tu */
+typedef  struct tu_iter {
+  struct tu_arr **tu;                                 /* current tuple array */
+  ub2             offset;                         /* offset of current tuple */
+  ub2             n;                         /* number of features per tuple */
+  ub4            *count;                         /* number of tuples in list */
+  feature        *fe;                                       /* current tuple */
+} tu_iter;
+
+/* names of features, for output */
+static const char feature_name[] =
+"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+
+
+/*
+------------------------------------------------------------------------------
+Stuff specific to jenny
+------------------------------------------------------------------------------
+*/
+
+#define MAX_FEATURES 52                  /* can't name more than 52 features */
+#define MAX_TESTS    65534         /* arbitrary limit on number of testcases */
+#define MAX_N        32       /* never can do complete coverage of 33-tuples */
+#define MAX_WITHOUT  (MAX_FEATURES*MAX_N)       /* max features in a without */
+#define MAX_DIMENSIONS (((ub2)~0)-1) /* More than 64K dimensions needs a ub4 */
+
+/* A "test", which is a combination of features.  Prefix t. */
+typedef  struct test {
+  ub2         *f;                                   /* features in this test */
+} test;
+
+/* representation of a restriction, prefix w */
+typedef  struct without {
+  ub2             len;                            /* length of feature array */
+  struct feature *fe;                                       /* feature array */
+} without;
+
+/* without chain */
+typedef  struct wchain {
+  without       *w;
+  struct wchain *next;
+} wchain;
+
+
+/* Return a count of how many withouts are disobeyed. */
+/* Also set a pointer to a randomly chosen violated without */
+int count_withouts(
+test     *t,                                                /* test to check */
+wchain   *wc)                                                /* restrictions */
+{
+  ub4      count;                             /* count of disobeyed withouts */
+
+  for (count = 0; wc; wc = wc->next) {
+    without *w = wc->w;
+    ub4      i = 0;
+    int      match = TRUE;                   /* match the entire restriction */
+    while (i<w->len) {
+      int dimension_match = FALSE;                /* match in this dimension */
+      do {
+	if (t->f[w->fe[i].d] == w->fe[i].f) {
+	  dimension_match = TRUE;
+	}
+	++i;
+      } while (i<w->len && (w->fe[i].d == w->fe[i-1].d));
+      if (!dimension_match) {
+	match = FALSE;
+	break;
+      }
+    }
+    if (match) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+
+/* token definitions */
+typedef enum token_type {
+  TOKEN_ERROR = 0,                                /* TOKEN_ERROR has to be 0 */
+  TOKEN_END,
+  TOKEN_NUMBER,
+  TOKEN_FEATURE,
+  TOKEN_SPACE
+} token_type;
+
+
+/* whole current state, prefix s */
+typedef  struct state {
+  ub1       n_final;           /* The n in the user's n-tuples, default is 2 */
+  ub2       ndim;                                    /* number of dimensions */
+  ub2       ntests;                              /* number of testcases in t */
+  ub1     **n;  /* n[d][f] is current n-tuple size for dimension d feature f */
+  test    **t;                             /* all the tests generated so far */
+  ub2      *dim;                     /* number of features in each dimension */
+  wchain  **wc;                   /* s->wc[d] lists withouts for dimension d */
+  wchain   *wc2;                      /* a list of all the original withouts */
+  wchain   *wc3;                             /* additional, deduced withouts */
+  tu_arr ***tu;  /* tu[d][f] lists untested tuples for dimension d feature f */
+  tu_arr ***one;
+       /* one[testcase][d] lists tuples with d covered only by this testcase */
+  ub4     **onec;          /* onec[testcase][d] is count of one[testcase][d] */
+  ub4     **used;
+   /* used[testcase][d] = pass# if this pass has already explored test[t][d] */
+  ub4     **tc;   /* tc[d][f] is # untested tulpes for dimension d feature f */
+  test     *tuple_tester;   /* an all -1 test used to test individual tuples */
+  ub2      *dimord;                   /* order in which to choose dimensions */
+  ub2      *featord;                    /* order in which to choose features */
+  flearandctx   r;                                  /* random number context */
+} state;
+
+
+void my_free( char *x)
+{
+  free(x);
+}
+
+/* zero out a list of tuples */
+void truncate_tuple( tu_arr **tu, ub4 *count)
+{
+  while (*tu) {
+    tu_arr *tu2 = *tu;
+    *tu = ((*tu)->next);
+    my_free((char *)tu2);
+  }
+  *count = 0;
+}
+
+/* delete the i-th test */
+void delete_test( state *s, ub4 i)
+{
+  test *t = s->t[i];
+
+  s->t[i] = s->t[--s->ntests];
+  my_free((char *)t->f);
+  my_free((char *)t);
+  if (s->one[s->ntests]) {
+    ub2   d;
+    for (d=0; d<s->ndim; ++d) {
+      truncate_tuple(&s->one[s->ntests][d], &s->onec[s->ntests][d]);
+    }
+    my_free((char *)s->one[s->ntests]);
+    my_free((char *)s->onec[s->ntests]);
+    s->one[s->ntests]  = (tu_arr **)0;
+    s->onec[s->ntests] = (ub4 *)0;
+  }
+}
+
+void cleanup(state *s)
+{
+  if (s->tu) {
+    ub2 d,f;
+    for (d=0; d<s->ndim; ++d) {
+      if (s->tu[d]) {
+	for (f=0; f<s->dim[d]; ++f) {
+	  truncate_tuple(&s->tu[d][f], &s->tc[d][f]);
+	}
+	my_free((char *)s->tu[d]);
+      }
+    }
+    my_free((char *)s->tu);
+  }
+
+  /* free n, the tuple lengths */
+  if (s->n) {
+    ub2 d;
+    for (d=0; d<s->ndim; ++d) {
+      if (s->n[d]) {
+	my_free((char *)s->n[d]);
+      }
+    }
+    my_free((char *)s->n);
+  }
+
+  /* free tc, count of uncovered tuples */
+  if (s->tc) {
+    ub2 d;
+    for (d=0; d<s->ndim; ++d) {
+      if (s->tc[d]) {
+	my_free((char *)s->tc[d]);
+      }
+    }
+    my_free((char *)s->tc);
+  }
+
+  /* free the secondary chains of restrictions */
+  if (s->wc) {
+    ub2 i;
+    for (i=0; i<s->ndim; ++i) {
+      while (s->wc[i]) {
+	wchain  *wc = s->wc[i];
+	s->wc[i] = s->wc[i]->next;
+	my_free((char *)wc);
+      }
+    }
+    my_free((char *)s->wc);
+  }
+
+  /* free all the actual restrictions */
+  while (s->wc2) {
+    wchain  *wc = s->wc2;
+    without *w = wc->w;
+    s->wc2 = s->wc2->next;
+    if (w->fe) my_free((char *)w->fe);
+    my_free((char *)w);
+    my_free((char *)wc);
+  }
+
+  while (s->wc3) {
+    wchain  *wc = s->wc3;
+    without *w = wc->w;
+    s->wc3 = s->wc3->next;
+    if (w->fe) my_free((char *)w->fe);
+    my_free((char *)w);
+    my_free((char *)wc);
+  }
+
+  if (s->t) {
+    while (s->ntests) {
+      delete_test(s, 0);
+    }
+    my_free((char *)s->t);
+  }
+
+  if (s->one) {
+    my_free((char *)s->one);
+  }
+
+  if (s->onec) {
+    my_free((char *)s->onec);
+  }
+
+  if (s->tuple_tester) {
+    if (s->tuple_tester->f) {
+      my_free((char *)s->tuple_tester->f);
+    }
+    my_free((char *)s->tuple_tester);
+  }
+
+  if (s->dimord) {
+    my_free((char *)s->dimord);
+  }
+
+  if (s->featord) {
+    my_free((char *)s->featord);
+  }
+
+  /* free the array of dimension lengths */
+  if (s->dim) my_free((char *)s->dim);
+}
+
+
+char *my_alloc( state *s, size_t len)
+{
+  char *rsl;
+  if (!(rsl = (char *)malloc(len+sizeof(size_t)))) {
+    printf("jenny: could not allocate space\n");
+    cleanup(s);
+    exit(0);  
+  }
+  memset(rsl, 0x00, len);
+  return rsl;
+}
+
+/* insert a tuple into a tuple array */
+int insert_tuple( state *s, tu_iter *ctx, feature *tuple)
+{
+  ub4      i;
+  feature *fe;
+  ub1      n = ctx->n;
+  ub4      lim = TUPLE_ARRAY / n;
+
+  while (*ctx->tu && (*ctx->tu)->len == lim) {
+    ctx->tu = &((*ctx->tu)->next);
+  }
+  if (!*ctx->tu) {
+    if (!((*ctx->tu) = (tu_arr *)my_alloc(s, sizeof(tu_arr)))) {
+      return FALSE;
+    }
+    (*ctx->tu)->len = 0;
+    (*ctx->tu)->next = (tu_arr *)0;
+  }
+  fe = &(*ctx->tu)->fe[(*ctx->tu)->len*n];
+  for (i=0; i<n; ++i) {
+    fe[i].d = tuple[i].d;
+    fe[i].f = tuple[i].f;
+  }
+  ++(*ctx->tu)->len;
+  ++*ctx->count;
+  return TRUE;
+}
+
+/* print out a single tuple */
+void show_tuple( feature *fe, ub2 len)
+{
+  ub4 i;
+  for (i=0; i<len; ++i) {
+    printf(" %d%c", fe[i].d+1, feature_name[fe[i].f]);
+  }
+  printf(" \n");
+}
+
+/* delete a tuple from a tuple array */
+feature *delete_tuple( tu_iter *ctx)
+{
+  feature *fe;
+  ub4      i;
+  feature *tuple = ctx->fe;
+  ub1      n = ctx->n;
+
+  --(*ctx->tu)->len;
+  --*ctx->count;
+  fe = &(*ctx->tu)->fe[(*ctx->tu)->len * n];
+  for (i=0; i<n; ++i) {
+    tuple[i].d = fe[i].d;
+    tuple[i].f = fe[i].f;
+  }
+  if (!(*ctx->tu)->len) {
+    tu_arr *tu2 = *ctx->tu;
+    *ctx->tu = ((*ctx->tu)->next);
+    my_free((char *)tu2);
+  }
+
+  if (!*ctx->tu) {                                   /* freed the last block */
+    ctx->offset = 0;
+    ctx->fe = (feature *)0;
+    return ctx->fe;
+  } else if (tuple == fe) {
+    ctx->offset = 0;
+    ctx->fe = &(*ctx->tu)->fe[0];          /* freed this block, move to next */
+    return ctx->fe;
+  } else {
+    return tuple;                 /* moved a new tuple into the old location */
+  }
+}
+
+/* start a tuple iterator */
+feature *start_tuple( tu_iter *ctx, tu_arr **tu, ub4 n, ub4 *count)
+{
+  ctx->tu = tu;
+  ctx->offset = 0;
+  ctx->n = n;
+  ctx->count = count;
+
+  if (*tu) {
+    ctx->fe = (*tu)->fe;
+  } else {
+    ctx->fe = (feature *)0;
+  }
+  return ctx->fe;
+}
+
+/* get the next tuple from a tuple iterator (0 if no more tuples) */
+static feature *next_tuple( tu_iter *ctx)
+{
+  if (++ctx->offset < (*ctx->tu)->len) {
+    ctx->fe += ctx->n;
+  } else {
+    ctx->tu = &(*ctx->tu)->next;
+    ctx->offset = 0;
+    if (*ctx->tu && (*ctx->tu)->len) {
+      ctx->fe = (*ctx->tu)->fe;
+    } else {
+      ctx->fe = (feature *)0;
+    }
+  }
+  return ctx->fe;
+}
+
+
+/* test if this test covers this tuple */
+static int test_tuple( ub2 *test, feature *tuple, ub2 n)
+{
+  sb4 i;
+  for (i=0; i<n; ++i) {
+    if (tuple[i].f != test[tuple[i].d]) {
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
+
+/* test if first tuple (t1, n1) is a subset of second tuple (t2, n2) */
+int subset_tuple( feature *t1, ub1 n1, feature *t2, ub1 n2)
+{
+  sb4 i, j;
+  if (n2 < n1)
+    return FALSE;
+  for (i=0, j=0; i<n1; ++i) {
+    while (t1[i].d > t2[j].d) {
+      if (++j == n2)
+	return FALSE;
+    }
+    if (t1[i].d != t2[j].d || t1[i].f != t2[j].f) {
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
+
+void initialize( state *s)
+{
+  /* make all freeable pointers start out zero */
+  s->dim          = (ub2 *)0;
+  s->wc           = (wchain **)0;
+  s->wc2          = (wchain *)0;
+  s->wc3          = (wchain *)0;
+  s->tu           = (tu_arr ***)0;
+  s->one          = (tu_arr ***)0;
+  s->onec         = (ub4 **)0;
+  s->n            = (ub1 **)0;
+  s->tc           = (ub4 **)0;
+  s->t            = (test **)0;
+  s->tuple_tester = (test *)0;
+  s->dimord       = (ub2 *)0;
+  s->featord      = (ub2 *)0;
+
+  /* fill in default values */
+  s->ndim = (ub2)0;
+  s->n_final = 2;     /* guarantees that all pairs of dimensions are covered */
+  s->ntests = 0;
+  flearand_init(&s->r, 0);             /* initialize random number generator */
+}
+
+
+/* add one test to the list of tests */
+int add_test( state *s, test *t)
+{
+  ub4 i;
+  if (s->ntests == MAX_TESTS) {
+    return FALSE;
+  }
+  s->one[s->ntests] = (tu_arr **)my_alloc(s, sizeof(tu_arr *)*s->ndim);
+  s->onec[s->ntests] = (ub4 *)my_alloc(s, sizeof(ub4)*s->ndim);
+  for (i=0; i<s->ndim; ++i) {
+    s->one[s->ntests][i] = (tu_arr *)0;
+    s->onec[s->ntests][i] = 0;
+  }
+  s->t[s->ntests++] = t;
+  return TRUE;
+}
+
+/*
+ * parse a token
+ * Start at *curr in string *inp of length inl
+ * Adjust *curr to be after the just-parsed token
+ * Place the token value in *rsl
+ * Return the token type
+ */
+token_type parse_token(char *inp, ub4 inl, ub4 *curr, ub4 *rsl)
+{
+  char mychar;
+  ub4  i;
+
+  if (*curr == inl)
+    return TOKEN_END;
+  mychar = inp[*curr];
+
+  if (mychar == '\0') {
+    return TOKEN_END;
+  } else if (mychar == ' ' || mychar == '\t' || mychar == '\n') {
+    /*--------------------------------------------------------- parse spaces */
+    for (i=*curr+1; i < inl; ++i) {
+      mychar = inp[i];
+      if (!(mychar == ' ' || mychar == '\t' || mychar == '\n'))
+	break;
+    }
+    *curr = i;
+    return TOKEN_SPACE;
+  } else if (mychar >= '0' && mychar <= '9') {
+    /*------------------------------------------------------- parse a number */
+    ub4 i, number = 0;
+    for (i=*curr; i < inl && inp[i] >= '0' && inp[i] <= '9';  ++i) {
+      number = number*10 + (inp[i] - '0');
+    }
+    *curr = i;
+    *rsl = number;
+    return TOKEN_NUMBER;
+  } else if ((mychar >= 'a' && mychar <= 'z') ||
+	     (mychar >= 'A' && mychar <= 'Z')) {
+    /*------------------------------------------------- parse a feature name */
+    ub4 i;
+    for (i=0; i<MAX_FEATURES; ++i)
+      if (feature_name[i] == mychar)
+	break;
+    if (i == MAX_FEATURES) {
+      printf("jenny: the name '%c' is not used for any feature\n",
+	     mychar);
+	return TOKEN_ERROR;
+    }
+    *rsl = i;
+    ++*curr;
+    return TOKEN_FEATURE;
+  } else {
+    return TOKEN_ERROR;
+  }
+}
+
+#define BUFSIZE (MAX_DIMENSIONS*7+2)
+/* load old tests before generating new ones */
+int load( state *s, char *testfile)
+{
+  char  buf[BUFSIZE];            /* buffer holding a line read from the file */
+  FILE *f;
+  
+  if (testfile[0] == '\0') {
+    f = stdin;
+  } else {
+    f = fopen(testfile, "r");
+  }
+
+  if (!f) {
+    printf("jenny: file %s could not be opened\n", testfile);
+    return FALSE;
+  }
+
+  while (fgets(buf, BUFSIZE, f) && (buf[0] != '.')) {
+    ub4   curr = 0;                               /* current offset into buf */
+    ub4   value;                                              /* token value */
+    token_type token;                                          /* token type */
+    ub4   i;
+    test *t;
+
+    t = (test *)my_alloc( s, sizeof(test));
+    t->f = (ub2 *)my_alloc( s, sizeof(ub2)*s->ndim);
+    if (!add_test(s, t)) {
+      goto failure;
+    }
+
+    for (i=0; i<s->ndim; ++i) {
+      if (parse_token(buf, UB4MAXVAL, &curr, &value) != TOKEN_SPACE) {
+	printf("jenny: -o, non-space found where space expected\n");
+	goto failure;
+      }
+      if (parse_token(buf, UB4MAXVAL, &curr, &value) != TOKEN_NUMBER) {
+	printf("jenny: -o, non-number found where number expected\n");
+	goto failure;
+      }
+      if (value-1 != i) {
+	printf("jenny: -o, number %d found out-of-place\n", value);
+	goto failure;
+      }
+      if (parse_token(buf, UB4MAXVAL, &curr, &value) != TOKEN_FEATURE) {
+	printf("jenny: -o, non-feature found where feature expected\n");
+	goto failure;
+      }
+      if (value >= s->dim[i]) {
+	printf("jenny: -o, feature %c does not exist in dimension %d\n", 
+	       feature_name[value], i+1);
+	goto failure;
+      }
+      t->f[i] = value;
+    }
+    if (parse_token(buf, UB4MAXVAL, &curr, &value) != TOKEN_SPACE) {
+      printf("jenny: -o, non-space found where trailing space expected\n");
+      goto failure;
+    }
+    if (parse_token(buf, UB4MAXVAL, &curr, &value) != TOKEN_END) {
+      printf("jenny: -o, testcase not properly terminated\n");
+      goto failure;
+    }
+
+    /* make sure the testcase obeys all the withouts */
+    if (count_withouts(t, s->wc2)) {
+      printf("jenny: -o, old testcase contains some without\n");
+      goto failure;
+    }
+  }
+
+  (void)fclose(f);
+  return TRUE;
+
+ failure:
+  while (fgets(buf, BUFSIZE, f) && (buf[0] != '.'))
+    ;                                            /* finish reading the input */
+  (void)fclose(f);                                         /* close the file */
+  return FALSE;
+}
+
+static const sb1 *jenny_doc[] = {
+  "jenny:\n",
+  "  Given a set of feature dimensions and withouts, produce tests\n",
+  "  covering all n-tuples of features where all features come from\n",
+  "  different dimensions.  For example (=, <, >, <=, >=, !=) is a\n",
+  "  dimension with 6 features.  The type of the left-hand argument is\n",
+  "  another dimension.  Dimensions are numbered 1..65535, in the order\n",
+  "  they are listed.  Features are implicitly named a..z, A..Z.\n",
+  "   3 Dimensions are given by the number of features in that dimension.\n",
+  "  -h prints out these instructions.\n",
+  "  -n specifies the n in n-tuple.  The default is 2 (meaning pairs).\n",
+  "  -w gives withouts.  -w1b4ab says that combining the second feature\n",
+  "     of the first dimension with the first or second feature of the\n",
+  "     fourth dimension is disallowed.\n",
+  "  -ofoo.txt reads old jenny testcases from file foo.txt and extends them.",
+  "\n\n",
+  "  The output is a testcase per line, one feature per dimension per\n",
+  "  testcase, followed by the list of all allowed tuples that jenny could\n",
+  "  not reach.\n",
+  "\n",
+  "  Example: jenny -n3 3 2 2 -w2b3b 5 3 -w1c3b4ace5ac 8 2 2 3 2\n",
+  "  This gives ten dimensions, asks that for any three dimensions all\n",
+  "  combinations of features (one feature per dimension) be covered,\n",
+  "  plus it asks that certain combinations of features\n",
+  "  (like (1c,3b,4c,5c)) not be covered.\n",
+  "\n"
+};
+
+
+/* parse -n, the tuple size */
+int parse_n( state *s, char *myarg)
+{
+  ub4 curr = 0;
+  ub4 temp = UB4MAXVAL;
+  token_type token;
+  ub4 dummy;
+
+  if ((token=parse_token(myarg, UB4MAXVAL, &curr, &temp)) != TOKEN_NUMBER) {
+    printf("jenny: -n should give an integer in 1..32, for example, -n2.\n");
+    return FALSE;
+  }
+  if ((token=parse_token(myarg, UB4MAXVAL, &curr, &dummy)) != TOKEN_END) {
+    printf("jenny: -n should be followed by just an integer\n");
+    return FALSE;
+  }
+  
+  if ((temp < 1) || (temp > 32)) {
+    printf("jenny: -n says all n-tuples should be covered.\n");
+    return FALSE;
+  }
+  if (temp > s->ndim) {
+    printf("jenny: -n, %ld-tuples are impossible with only %d dimensions\n",
+	   temp, s->ndim);
+    return FALSE;
+  }
+  s->n_final = (ub2)temp;
+  return TRUE;
+}
+
+
+
+/* parse -w, a without */
+int parse_w( state *s, sb1 *myarg)
+{
+  without   *w;
+  wchain    *wc;
+  feature    fe[MAX_WITHOUT];
+  ub1        used[MAX_DIMENSIONS];
+  ub4        dimension_number;
+  ub4        curr = 0;
+  ub4        fe_len, value;
+  ub4        i, j, k;
+  size_t     len = strlen(myarg);
+  token_type t = parse_token(myarg, len, &curr, &value);
+  
+  for (i=0; i<s->ndim; ++i)
+    used[i] = FALSE;
+  if (t != TOKEN_NUMBER) {
+    printf("jenny: -w is <number><features><number><features>...\n");
+    printf("jenny: -w must start with an integer (1 to #dimensions)\n");
+    return FALSE;
+  }
+  fe_len=0;
+  
+ number:
+  dimension_number = --value;
+  if (dimension_number >= s->ndim) {
+    printf("jenny: -w, dimension %ld does not exist, ", dimension_number+1);
+    printf("you gave only %d dimensions\n", s->ndim);
+    return FALSE;
+  }
+  if (used[dimension_number]) {
+    printf("jenny: -w, dimension %d was given twice in a single without\n",
+	   dimension_number+1);
+    return FALSE;
+  }
+  used[dimension_number] = TRUE;
+  
+  
+  switch (parse_token(myarg, len, &curr, &value)) {
+  case TOKEN_FEATURE: goto feature;
+  case TOKEN_END:
+    printf("jenny: -w, withouts must follow numbers with features\n");
+    return FALSE;
+  default:
+    printf("jenny: -w, unexpected without syntax\n");
+    printf("jenny: proper withouts look like -w2a1bc99a\n");
+    return FALSE;
+  }
+  
+ feature:
+  if (value >= s->dim[dimension_number]) {
+    printf("jenny: -w, there is no feature '%c' in dimension %d\n",
+	   feature_name[value], dimension_number+1);
+    return FALSE;
+  }
+  fe[fe_len].d = dimension_number;
+  fe[fe_len].f = value;
+  if (++fe_len >= MAX_WITHOUT) {
+    printf("jenny: -w, at most %d features in a single without\n",
+	   MAX_WITHOUT);
+    return FALSE;
+  }
+
+  
+  switch (parse_token(myarg, len, &curr, &value)) {
+  case TOKEN_FEATURE: goto feature;
+  case TOKEN_NUMBER: goto number;
+  case TOKEN_END: goto end;
+  default:
+    printf("jenny: -w, unexpected without syntax\n");
+    printf("jenny: proper withouts look like -w2a1bc99a\n");
+    return FALSE;
+  }
+  
+ end:
+
+  /* sort the dimensions and features in this restriction */
+  for (i=0; i<fe_len; ++i) {
+    for (j=i+1; j<fe_len; ++j) {
+      if ((fe[i].d > fe[j].d) ||
+	  ((fe[i].d == fe[j].d) && (fe[i].f > fe[j].f))) {
+	ub2 fe_temp;
+	fe_temp = fe[i].d;
+	fe[i].d = fe[j].d;
+	fe[j].d = fe_temp;
+	fe_temp = fe[i].f;
+	fe[i].f = fe[j].f;
+	fe[j].f = fe_temp;
+      }
+    }
+  }
+
+  /* allocate a without */
+  w = (without *)my_alloc( s, sizeof(without));
+  wc = (wchain *)my_alloc( s, sizeof(wchain));
+  wc->next = s->wc2;
+  wc->w = w;
+  w->len = fe_len;
+  w->fe = (feature *)my_alloc( s, sizeof(feature)*fe_len);
+  for (i=0; i<fe_len; ++i) {
+    w->fe[i].d = fe[i].d;
+    w->fe[i].f = fe[i].f;
+  }
+  s->wc2 = wc;
+
+  return TRUE;
+}
+
+/* parse -s, a seed for the random number generator */
+int parse_s( state *s, sb1 *myarg)
+{
+  ub4 seed = 0;
+  ub4 dummy = 0;
+  ub4 curr = 0;
+  if (parse_token( myarg, UB4MAXVAL, &curr, &seed) != TOKEN_NUMBER) {
+    printf("jenny: -s must be followed by a positive integer\n");
+    return FALSE;
+  }
+  if (parse_token( myarg, UB4MAXVAL, &curr, &dummy) != TOKEN_END) {
+    printf("jenny: -s should give just an integer, example -s123\n");
+    return FALSE;
+  }
+  flearand_init(&s->r, seed);          /* initialize random number generator */
+  return TRUE;
+}
+
+void preliminary( state *s)
+{
+  wchain  *wc;
+  ub4      d;
+
+  s->tuple_tester = (test *)my_alloc( s, sizeof(test));
+  s->tuple_tester->f = (ub2 *)my_alloc( s, sizeof(ub2)*s->ndim);
+  s->dimord  = (ub2 *)my_alloc( s, sizeof(ub2)*s->ndim);
+  s->wc = (wchain **)my_alloc( s, sizeof(wchain *)*s->ndim);
+  s->tu = (tu_arr ***)my_alloc( s, sizeof(tu_arr **)*s->ndim);
+  s->one = (tu_arr ***)my_alloc( s, sizeof(tu_arr **)*MAX_TESTS);
+  s->n  = (ub1 **)my_alloc( s, sizeof(ub1 *)*s->ndim);
+  s->onec = (ub4 **)my_alloc( s, sizeof(ub4 *)*MAX_TESTS);
+  s->tc = (ub4 **)my_alloc( s, sizeof(ub4 *)*s->ndim);
+  s->t  = (test **)my_alloc( s, sizeof(test *)*MAX_TESTS);
+
+  /* initialize to safe values before doing further allocations */
+  for (d=0; d<s->ndim; ++d) {
+    s->tuple_tester->f[d] = (ub2)~0;
+    s->dimord[d] = (ub2)d;
+    s->wc[d] = (wchain *)0;
+    s->tu[d] = (tu_arr **)0;
+    s->n[d] = (ub1 *)0;
+    s->tc[d] = (ub4 *)0;
+  }
+
+  s->featord = (ub2 *)my_alloc( s, sizeof(ub2)*MAX_FEATURES);
+
+  /* allocate roots for feature-specific lists of uncovered tuples */
+  for (d=0; d<s->ndim; ++d) {
+    ub2 f;
+    s->tu[d] = (tu_arr **)my_alloc( s, sizeof(tu_arr *)*s->dim[d]);
+    s->n[d]  = (ub1 *)my_alloc(s, sizeof(ub1)*s->dim[d]);
+    s->tc[d] = (ub4 *)my_alloc(s, sizeof(ub4)*s->dim[d]);
+    for (f=0; f<s->dim[d]; ++f) {
+      s->tu[d][f] = (tu_arr *)0;
+      s->n[d][f]  = 0;
+      s->tc[d][f] = 0;
+    }
+  }
+
+  /* make dimension-specific lists of withouts */
+  for (wc=s->wc2; wc; wc=wc->next) {
+    without *w = wc->w;
+    int      old = -1;
+    int      i;
+    for (i=0; i<w->len; ++i) {
+      if (w->fe[i].d != old) {
+	wchain *wcx = (wchain *)my_alloc( s, sizeof(wchain));
+	wcx->w = w;
+	wcx->next = s->wc[w->fe[i].d];
+	s->wc[w->fe[i].d] = wcx;
+	old = w->fe[i].d;
+      }
+    }
+  }
+}
+
+/* parse the inputs */
+int parse( int argc, char *argv[], state *s)
+{
+  int   i, j;
+  ub4   temp;
+  char *testfile = (char *)0;
+
+  /* internal check: we have MAX_FEATURES names for features */
+  if (strlen(feature_name) != MAX_FEATURES) {
+    printf("feature_name length is wrong, %d\n", strlen(feature_name));
+    return FALSE;
+  }
+
+  /* How many dimensions are there?  Set ndim, allocate space for dim.  */
+  for (temp=0, i=1; i<argc; ++i) {
+    if (argv[i][0] >= '0' && argv[i][0] <= '9') {
+      ++temp;
+    }
+  }
+  if (temp > MAX_DIMENSIONS) {
+    printf("jenny: maximum number of dimensions is %ld.  %ld is too many.\n",
+	   MAX_DIMENSIONS, temp);
+    return FALSE;
+  }
+  s->ndim = (ub2)temp;
+  s->dim = (ub2 *)my_alloc( s, sizeof(ub2)*(s->ndim));
+
+  /* Read the lengths of all the dimensions */
+  for (i=1, j=0; i<argc; ++i) {
+    if (argv[i][0] >= '0' && argv[i][0] <= '9') {        /* dimension length */
+      sb1 *myarg = argv[i];
+      ub4  dummy;
+      ub4  curr = 0;
+
+      (void)parse_token(myarg, UB4MAXVAL, &curr, &temp);
+      if (parse_token(myarg, UB4MAXVAL, &curr, &dummy) != TOKEN_END) {
+	printf("jenny: something was trailing a dimension number\n");
+	return FALSE;
+      }
+      if (temp > MAX_FEATURES) {
+	printf("jenny: dimensions must be smaller than %d.  %ld is too big.\n",
+	       MAX_FEATURES, temp);
+	return FALSE;
+      }
+      if (temp < 2) {
+	printf("jenny: a dimension must have at least 2 features, not %d\n",
+	       temp);
+	return FALSE;
+      }
+      s->dim[j++] = (ub2)temp;
+    } else if (argv[i][1] == 'h') {
+      int i;
+      for (i=0; i<(sizeof(jenny_doc)/sizeof(ub1 *)); ++i) {
+	printf(jenny_doc[i]);
+      }
+      return FALSE;
+    }
+  }
+
+  /* Read the rest of the arguments */
+  for (i=1; i<argc; ++i) if (argv[i][0] == '-') {        /* dimension length */
+    switch(argv[i][1]) {
+    case '\0':
+      printf("jenny: '-' by itself isn't a proper argument.\n");
+      return FALSE;
+    case 'o':                               /* -o, file containing old tests */
+      testfile = &argv[i][2];
+      break;
+    case 'n':                       /* -n, get the n of "cover all n-tuples" */
+      if (!parse_n( s, &argv[i][2])) return FALSE;
+      break;
+    case 'w':                                /* -w, "without", a restriction */
+      if (!parse_w( s, &argv[i][2])) return FALSE;
+      break;
+    case 's':                           /* -s, "random", change the behavior */
+      if (!parse_s( s, &argv[i][2])) return FALSE;
+      break;
+    default:
+      printf("jenny: legal arguments are numbers, -n, -s, -w, -h, not -%c\n",
+	     argv[i][1]);
+      return FALSE;
+    }
+  }                                            /* for (each argument) if '-' */
+
+  if (s->n_final > s->ndim) {
+    printf("jenny: %ld-tuples are impossible with only %d dimensions\n",
+	   s->n_final, s->ndim);
+    return FALSE;
+  }
+
+  preliminary(s);            /* allocate structures, do preliminary analysis */
+
+  /* read in any old tests so we can build from that base */
+  if (testfile) {
+    if (!load( s, testfile)) {
+      return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+/* print out a single test */
+void report( test *t, ub2 len)
+{
+  ub4 i;
+  for (i=0; i<len; ++i) {
+    printf(" %d%c", i+1, feature_name[t->f[i]]);
+  }
+  printf(" \n");
+}
+
+/* print out all the tests */
+void report_all( state *s)
+{
+  ub4   i;
+  for (i=0; i<s->ntests; ++i) {
+    report(s->t[i], s->ndim);
+  }
+}
+
+
+void start_builder( state *s, feature *tuple, ub1 n)
+{
+  ub1 i;
+  for (i=0; i<n; ++i) {
+    tuple[i].d = i;
+    tuple[i].f = 0;
+  }
+}
+
+int next_builder( state *s, feature *tuple, ub1 n)
+{
+  sb4 i = n;
+
+  while (i-- &&
+	 tuple[i].d == s->ndim-n+i &&
+	 tuple[i].f == s->dim[tuple[i].d]-1)
+    ;
+  if (i == -1)
+    return FALSE;
+  else if (tuple[i].f < s->dim[tuple[i].d]-1) {
+    ++tuple[i].f;                                       /* increment feature */
+    for (i; i<n-1; ++i) {            /* reset all less significant positions */
+      tuple[i+1].d = tuple[i].d+1;
+      tuple[i+1].f = 0;
+    }
+  }
+  else {
+    ++tuple[i].d;      /* increment least significant non-maxed-out position */
+    tuple[i].f = (ub2)0;                                /* reset its feature */
+    for (i; i<n-1; ++i) {            /* reset all less significant positions */
+      tuple[i+1].d = tuple[i].d+1;
+      tuple[i+1].f = 0;
+    }
+  }
+  return TRUE;
+}
+
+
+void build_tuples( state *s, ub2 d, ub2 f)
+{
+  feature  offset[MAX_N];                                      /* n-1-tuples */
+  feature  tuple[MAX_N];                      /* n-tuples that include (d,f) */
+  sb4      i, j, n = s->n[d][f];
+  ub8      count = 0;
+  test    *t;
+  tu_iter  ctx;
+
+  if (s->tc[d][f] > 0 || s->n[d][f] == s->n_final) {
+    return;                               /* no need to generate more tuples */
+  }
+
+  n = ++s->n[d][f];                        /* move up to a bigger tuple size */
+
+  /* get ready to insert tuples into the tuple list for (d,f) */
+  start_tuple(&ctx, &s->tu[d][f], n, &s->tc[d][f]);
+  for (i=0; i<n; ++i) {
+    tuple[i].d = 0;
+    tuple[i].f = 0;
+  }
+
+  /* Offset iterates through all n-1-tuples.  Inject (d,f) into each. */
+  start_builder( s, offset, n-1);
+  for (;;) {
+    for (i=0; (i<n-1) && (offset[i].d < d); ++i) {
+      tuple[i].d = offset[i].d;
+      tuple[i].f = offset[i].f;
+    }
+    /* can't inject (d,f) into a tuple that already has d */
+    if ((i<n-1) && (offset[i].d == d))
+      goto make_next_tuple;
+    tuple[i].d = d;
+    tuple[i].f = f;
+    for (++i; i<n; ++i) {
+      tuple[i].d = offset[i-1].d;
+      tuple[i].f = offset[i-1].f;
+    }
+
+    for (i=0; i<n; ++i) {
+      s->tuple_tester->f[tuple[i].d] = tuple[i].f;
+    }
+    if (count_withouts(s->tuple_tester, s->wc2) ||
+	count_withouts(s->tuple_tester, s->wc3))
+      goto make_next_tuple;
+
+    /* is this tuple covered by the existing tests? */
+    for (j=0; j<s->ntests; ++j) {
+      test *t = s->t[j];
+      for (i=0; i<n; ++i) {
+	if (t->f[tuple[i].d] != tuple[i].f) {
+	  break;
+	}
+      }
+      if (i == n) {
+	goto make_next_tuple;
+      }
+    }
+
+    /* add it to the list of uncovered tuples */
+    if (!insert_tuple(s, &ctx, tuple)) {
+      printf("jenny: could not insert tuple\n");
+      return;
+    }
+    ++count;
+
+    /* next tuple */
+  make_next_tuple:
+    for (i=0; i<n; ++i) {
+      s->tuple_tester->f[tuple[i].d] = (ub2)~0;
+    }
+    if (!next_builder(s, offset, n-1))
+      break;
+  }
+}
+
+/*
+ * Tweak the test, other than entries in tuple, so that all withouts are
+ * obeyed.
+ *
+ * Algorithm: loop through all the dimensions touched by any without, changing
+ * features in those dimensions so that the total number of withouts disobeyed
+ * decreases or stays the same.  Succeed if all withouts are obeyed.  Give up
+ * after MAX_NO_PROGRESS consecutive loops that fail to decrease the number of
+ * withouts disobeyed.
+ *
+ * Would it be better to find some disobeyed without, and change one of its
+ * dimensions at random?  No.  Consider
+ *   jenny 2 2 2 -w1a2a -w1b3a -w1b3a -w2b3a -w2b3a
+ * and tentative testcase
+ *   1a 2a 3a
+ * Progress is impossible unless you change a dimension that is not currently
+ * in any disobeyed without.
+ */
+#define MAX_NO_PROGRESS 2
+int obey_withouts(
+state *s,                                                    /* global state */
+test  *t,                                                /* test being built */
+ub1   *mut)              /* mut[i] = 1 if I am allowed to adjust dimension i */
+{
+  ub4      i;
+  without *w;                               /* one of the disobeyed withouts */
+  ub4      count;                        /* number of withouts currently hit */
+  ub2      ndim;                                         /* size of dimord[] */
+  ub2      temp;
+
+  /* how many withouts are currently disobeyed? */
+  if (!count_withouts(t, s->wc2))
+    return TRUE;
+
+  /* fill dimord[] with all dimensions that can and should be tweaked */
+  for (ndim=0, i=0; i<s->ndim; ++i) {
+    if (mut[i] && s->wc[i]) {
+      s->dimord[ndim++] = i;
+    }
+  }
+
+  /* hillclimbing, with sidestepping, minimize number of withouts hit */
+  for (i=0; i<MAX_NO_PROGRESS; ++i) {
+    ub4     j;
+    ub2     best[MAX_FEATURES];                      /* best features so far */
+    ub1     ok = TRUE;
+    
+    for (j=ndim; j>0; --j) {
+      ub2 fcount = 0;                    /* count of filled elements of best */
+      ub2 mydim;                                    /* the current dimension */
+      ub2 k;
+
+      /* walk the dimensions in a random order, no replacement */
+      mydim = flearand(&s->r) % j;
+      temp = s->dimord[mydim];
+      s->dimord[mydim] = s->dimord[j-1];
+      s->dimord[j-1] = temp;
+      mydim = s->dimord[j-1];
+
+      /* see how many withouts this dimension is disobeying */
+      count = count_withouts(t, s->wc[mydim]);
+
+      /* test every feature of this dimension, trying to make progress */
+      for (k=0; k<s->dim[mydim]; ++k) {
+	ub2 newcount;
+	t->f[mydim] = k;
+	newcount = count_withouts(t, s->wc[mydim]);
+	if (newcount <= count) {
+	  if (newcount < count) {
+	    i = 0;                                      /* partial progress! */
+	    fcount = 0;
+	    count = newcount;
+	  }
+	  best[fcount++] = k;
+	}
+      }
+
+      /* choose one of the best features for this dimension at random */
+      if (fcount == 0) {
+	printf("jenny: internal error a\n");
+      } else if (fcount == 1) {
+	t->f[mydim] = best[0];
+      } else {
+	temp = (flearand(&s->r) % fcount);
+	t->f[mydim] = best[temp];
+      }
+
+      if (count > 0)
+	ok = FALSE;
+    }
+    if (ok) {                                       /* no withouts disobeyed */
+      return TRUE;
+    }
+  }
+  return FALSE;               /* failure, could not satisfy all the withouts */
+}
+
+ub4 count_tuples( state *s, test *t, int d, int f)
+{
+  ub4      count = 0;
+  ub1      n = s->n[d][f];
+  tu_iter  ctx;
+  feature *this = start_tuple(&ctx, &s->tu[d][f], n, &s->tc[d][f]);
+  while (this) {
+    count += test_tuple(t->f, this, n);
+    this = next_tuple(&ctx);
+  }
+  return count;
+}
+
+ub4 maximize_coverage( 
+state *s,                                                    /* global state */
+test  *t,            /* testcase being built, already obeys all restrictions */
+ub1   *mut,                        /* mut[i] = 1 if I can adjust dimension i */
+ub1    n)                            /* size of smallest tuple left to cover */
+{
+  ub1 progress;
+  ub2 ndim;
+  ub2 i;
+  ub4 total;
+
+  /* build a list of all the dimensions that we can modify */
+  for (ndim=0, i=0; i<s->ndim; ++i) {
+    if (mut[i]) {
+      s->dimord[ndim++] = i;
+    }
+  }
+
+  /* repeatedly loop through all dimensions, maximizing tuple coverage */
+  do {
+    progress = FALSE;             /* assume no improvement in tuple coverage */
+    total    = 1; /* one, for the one fixed tuple we are guaranteed to cover */
+
+    /* scramble the array of dimensions; */
+    for (i=ndim; i>1; --i) {
+      ub2 j = flearand(&s->r) % i;
+      ub2 temp = s->dimord[i-1];
+      s->dimord[i-1] = s->dimord[j];
+      s->dimord[j] = temp;
+    }
+
+    /* for every dimension that we can adjust */
+    for (i=0; i<ndim; ++i) {
+      ub2 best[MAX_FEATURES];     /* list of features with the best coverage */
+      ub2 count = 0;                                       /* size of best[] */
+      ub2 d = s->dimord[i];
+      ub1 best_n = s->n[d][t->f[d]];
+      ub4 coverage = count_tuples(s, t, d, t->f[d]);
+      ub4 f;
+
+      /* for every feature in mydim, see if using it would improve coverage */
+      for (f=0; f<s->dim[d]; ++f) {
+	t->f[d] = f;                            /* switch to the new feature */
+	if (!count_withouts(t, s->wc[d])) {         /* need to obey withouts */
+	  ub4 new_coverage = count_tuples(s, t, d, f);
+	  if (s->n[d][f] < best_n) {
+	    best_n = s->n[d][f];
+	    progress = TRUE;
+	    coverage = new_coverage;
+	    count = 0;
+	    best[count++] = f;
+	  } else if (s->n[d][f] == best_n && new_coverage >= coverage) {
+	    if (new_coverage > coverage) {
+	      progress = TRUE;
+	      coverage = new_coverage;
+	      count = 0;
+	    }
+	    best[count++] = f;
+	  }
+	}
+      }
+
+      /* 
+       * Change this dimension to the best features seen.
+       * Worst case, everyone was worse than the old value, so best[0] will
+       * be the old value.  Coverage will be the same and still no withouts
+       * will be hit.
+       */
+      if (count == 0) {
+	printf("jenny: internal error b\n");
+      } else if (count == 1) {
+	t->f[d] = best[0];
+      } else {
+	t->f[d] = best[flearand(&s->r) % count];
+      }
+      if (s->n[d][t->f[d]] == n)
+	total += coverage;
+    }
+
+  } while (progress);
+  return total;
+}
+
+
+/*
+ * Generate one test that obeys all the restrictions and covers at 
+ * least one tuple.  Do not add it to the list of tests yet.  Return FALSE
+ * if we couldn't satisfy the withouts while covering this tuple.
+ */
+#define MAX_ITERS 10
+ub4 generate_test( state *s, test *t, feature *tuple, ub1 n)
+{
+  int  iter, i;
+  ub1  mut[MAX_DIMENSIONS];        /* mut[i] = 1 if I can adjust dimension i */
+  ub4  coverage = 0;
+
+  /* mut[i] = 1 if I can modify dimension i */
+  for (i=0; i<s->ndim; ++i) mut[i] = 1;
+  for (i=0; i<n; ++i) mut[tuple[i].d] = 0;
+  
+  for (iter=0; iter<MAX_ITERS; ++iter) {
+    /* Produce a totally random testcase */
+    for (i=0; i<s->ndim; ++i) {
+      t->f[i] = flearand(&s->r) % (s->dim[i]);
+    }
+    
+    /* Plug in the chosen new tuple */
+    for (i=0; i<n; ++i){
+      t->f[tuple[i].d] = tuple[i].f;
+    }
+
+    /* If we can get all the withouts obeyed, break, success */
+    if (!s->wc2 || obey_withouts(s, t, mut)) {
+      if (count_withouts(t, s->wc2)) {
+	printf("internal error without %d\n", s->wc2);
+      }
+      break;
+    }
+  }
+
+  /* quit if we were unable to satisfy the withouts */
+  if (iter == MAX_ITERS) {
+    goto done;
+  }
+
+  /*
+   * We now have a test that covers the new tuple and satisfies withouts.
+   * Do hillclimbing to cover as many new tuples as possible.
+   */
+  coverage = maximize_coverage(s, t, mut, n);
+
+ done:
+  return coverage;
+}
+
+#define GROUP_SIZE 5
+void cover_tuples( state *s)
+{
+  test *curr_test;
+  curr_test = (test *)my_alloc( s, sizeof(test));
+  curr_test->f = (ub2 *)my_alloc( s, sizeof(ub2)*s->ndim);
+
+  while (TRUE) {
+    ub4         i;
+    ub2         d;
+    test       *best_test;
+    sb4         best_count = -1;
+    ub1         tuple_n = MAX_N;
+    ub4         tuple_count = 0;
+    ub1         covered = FALSE;
+    feature    *tuple = (feature *)0;
+    ub4         tuple_f, tuple_d;
+
+    /* extend lists of tuples and choose one tuple to cover */
+    for (d=0; d<s->ndim; ++d) {
+      ub2 f;
+      for (f=0; f<s->dim[d]; ++f) {
+	build_tuples(s, d, f);
+	if (s->n[d][f] < tuple_n) {
+	  tuple_n = s->n[d][f];
+	  tuple_count = s->tc[d][f];
+	  tuple = s->tu[d][f]->fe;
+	  tuple_f = f;
+	  tuple_d = d;
+	} else if (s->n[d][f] == tuple_n && s->tc[d][f] > tuple_count) {
+	  tuple_count = s->tc[d][f];
+	  tuple = s->tu[d][f]->fe;
+	  tuple_f = f;
+	  tuple_d = d;
+	}
+      }
+    }
+
+    if (tuple_count == 0) {
+      if (tuple_n == s->n_final)
+	break;                             /* no more tuples to cover, done! */
+      else
+	continue;
+    }
+
+
+    best_test = (test *)my_alloc( s, sizeof(test));
+    best_test->f = (ub2 *)my_alloc( s, sizeof(ub2)*s->ndim);
+
+    /* find a good test */
+    for (i=0; i<GROUP_SIZE; ++i) {
+      tu_iter  ctx;
+      sb4      this_count;
+
+      /* generate a test that covers the first tuple */
+      if (!(this_count = generate_test(s, curr_test, tuple, tuple_n))) {
+	continue;
+      }
+      covered = TRUE;
+
+      /* see how many tuples are covered altogether */
+      if (this_count > best_count) {
+	test *temp = best_test;
+	best_test = curr_test;
+	curr_test = temp;
+
+	best_count = this_count;
+      }
+    }
+
+    if (!covered) {
+      wchain  *wc = (wchain *)my_alloc( s, sizeof(wchain));
+      without *w = (without *)my_alloc( s, sizeof(without));
+      feature  extra[MAX_N];
+
+      /* make a copy of tuple, because we'll be deleting it */
+      for (i=0; i<tuple_n; ++i) {
+	extra[i].d = tuple[i].d;
+	extra[i].f = tuple[i].f;
+      }
+
+      printf("Could not cover tuple ");
+      show_tuple(tuple, tuple_n);
+
+      /* add this tuple to the list of restrictions */
+      wc->w = w;
+      wc->next = s->wc3;
+      s->wc3 = wc;
+      w->fe = (feature *)0;
+      w->len = tuple_n;
+      w->fe = (feature *)my_alloc( s, sizeof(feature)*tuple_n);
+      for (i=0; i<tuple_n; ++i) {
+	w->fe[i].d = extra[i].d;
+	w->fe[i].f = extra[i].f;
+      }
+      
+      for (d=0; d<s->ndim; ++d) {
+	ub2      f;
+	tu_iter  ctx;
+	for (f=0; f<s->dim[d]; ++f) {
+	  ub1      n = s->n[d][f];
+	  feature *this = start_tuple(&ctx, &s->tu[d][f], n, &s->tc[d][f]);
+	
+	  /* remove all the tuples covered by it */
+	  while (this) {
+	    if (subset_tuple(extra, tuple_n, this, n)) {
+	      this = delete_tuple(&ctx);
+	    } else {
+	      this = next_tuple(&ctx);
+	    }
+	  }
+	}
+      }
+      my_free((char *)best_test->f);
+      my_free((char *)best_test);
+    } else {
+      ub2      d;
+      for (d=0; d<s->ndim; ++d) {
+	tu_iter  ctx;
+	ub2      f = best_test->f[d];
+	ub1      n = s->n[d][f];
+	feature *this = start_tuple(&ctx, &s->tu[d][f], n, &s->tc[d][f]);
+	
+	/* remove all the tuples covered by it */
+	while (this) {
+	  if (test_tuple(best_test->f, this, n)) {
+	    this = delete_tuple(&ctx);
+	  } else {
+	    this = next_tuple(&ctx);
+	  }
+	}
+      }
+
+      /* add it to the list of tests */
+      if (!add_test(s, best_test)) {
+	printf("jenny: exceeded maximum number of tests\n");
+	my_free((char *)curr_test->f);
+	my_free((char *)curr_test);
+	my_free((char *)best_test->f);
+	my_free((char *)best_test);
+	cleanup(s);
+	exit(0);
+      }
+    }
+  }
+
+  my_free((char *)curr_test->f);
+  my_free((char *)curr_test);
+}
+
+void prepare_reduce( state *s) 
+{
+  feature tuple[MAX_N];
+  ub1     n = s->n_final;
+  ub4     t, d;
+
+  for (t=0; t<s->ntests; ++t) {
+    for (d=0; d<s->ndim; ++d) {
+      s->onec[t][d] = 0;
+    }
+  }
+
+  /* Iterate through all the tuples */
+  start_builder( s, tuple, n);
+
+  for (;;) {
+    sb4 i;
+    ub2 thistest;
+
+    for (i=0; i<n; ++i) {
+      s->tuple_tester->f[tuple[i].d] = tuple[i].f;
+    }
+    if (count_withouts(s->tuple_tester, s->wc2) ||
+	count_withouts(s->tuple_tester, s->wc3))
+      goto make_next_tuple;
+
+    for (i=0; i<s->ntests; ++i) {
+      ub1 j;
+      for (j=0; j<n; ++j)
+	if (s->t[i]->f[tuple[j].d] != tuple[j].f)
+	  break;
+      if (j == n)
+	break;                              /* this test contains this tuple */
+    }
+
+    /* no tests cover this tuple */
+    if (i==s->ntests) {
+      printf("error: some tuple not covered at all\n");
+    } else {
+      thistest = i;
+      for (++i; i<s->ntests; ++i) {
+	ub1 j;
+	for (j=0; j<n; ++j)
+	  if (s->t[i]->f[tuple[j].d] != tuple[j].f)
+	    break;
+	if (j == n)
+	  break;                            /* this test contains this tuple */
+      }
+      if (i == s->ntests) {
+	ub1 j;
+	for (j=0; j<n; ++j) {
+	  tu_iter ctx;
+	  (void)start_tuple(&ctx, &s->one[thistest][tuple[j].d], n, 
+			    &s->onec[thistest][tuple[j].d]);
+	  (void)insert_tuple(s, &ctx, tuple);
+	}
+      }
+    }
+
+  make_next_tuple:
+    for (i=0; i<n; ++i) {
+      s->tuple_tester->f[tuple[i].d] = (ub2)~0;
+    }
+    if (!next_builder( s, tuple, n))
+      break;
+  }
+}
+
+/* find a test to try to eliminate */
+int which_test( state *s)
+{
+  ub4 t;
+  ub4 mincount = ~0;
+  ub4 mint = 0;                  /* test with the fewest once-covered tuples */
+  for (t=0; t<s->ntests; ++t) {
+    ub4 i, j=0;
+    for (i=0; i<s->ndim; ++i) {
+      j += s->onec[t][i];
+    }
+    if (j <= mincount) {
+      mincount = j;
+      mint = t;
+    }
+  }
+  return mint;
+}
+
+void reduce_tests( state *s) 
+{
+  ub4 t;
+  prepare_reduce( s);
+  t = which_test( s);
+}
+
+/* Confirm that every tuple is covered by either a testcase or a without */
+int confirm( state *s)
+{
+  feature  offset[MAX_N];
+  sb4      i, j, n = s->n_final;
+
+  /* Make a list of allowed but uncovered tuples */
+  for (i=0; i<n; ++i) {
+    offset[i].d = i;
+    offset[i].f = 0;
+  }
+
+  for (;;) {
+    for (i=0; i<n; ++i) {
+      s->tuple_tester->f[offset[i].d] = offset[i].f;
+    }
+    if (count_withouts(s->tuple_tester, s->wc2) ||
+	count_withouts(s->tuple_tester, s->wc3))
+      goto make_next_tuple;
+
+    /* is this tuple covered by the existing tests? */
+    for (j=0; j<s->ntests; ++j) {
+      test *t = s->t[j];
+      for (i=0; i<n; ++i) {
+	if (t->f[offset[i].d] != offset[i].f) {
+	  break;
+	}
+      }
+      if (i == n) {
+	goto make_next_tuple;
+      }
+    }
+
+    printf("problem with %d%c\n", offset[0].d+1, feature_name[offset[0].f]);
+    return FALSE;                       /* found a tuple that is not covered */
+
+  make_next_tuple:
+    for (i=0; i<n; ++i) {
+      s->tuple_tester->f[offset[i].d] = (ub2)~0;
+    }
+    i=n;
+    while (i-- &&
+	   offset[i].d == s->ndim-n+i &&
+	   offset[i].f == s->dim[offset[i].d]-1)
+      ;
+    if (i == -1)
+      break;                                                         /* done */
+    else if (offset[i].f < s->dim[offset[i].d]-1) {
+      ++offset[i].f;                                    /* increment feature */
+      for (i; i<n-1; ++i) {          /* reset all less significant positions */
+	offset[i+1].d = offset[i].d+1;
+	offset[i+1].f = 0;
+      }
+    }
+    else {
+      ++offset[i].d;   /* increment least significant non-maxed-out position */
+      offset[i].f = (ub2)0;                             /* reset its feature */
+      for (i; i<n-1; ++i) {          /* reset all less significant positions */
+	offset[i+1].d = offset[i].d+1;
+	offset[i+1].f = 0;
+      }
+    }
+  }
+
+  return TRUE;              /* all tuples are covered by a test or a without */
+}
+
+
+void driver( int argc, char *argv[])
+{
+  state s;                                                 /* internal state */
+
+  initialize(&s);
+
+  if (parse(argc, argv, &s)) {               /* read the user's instructions */
+    cover_tuples(&s);     /* generate testcases until all tuples are covered */
+    /* reduce_tests(&s); */         /* try to reduce the number of testcases */
+    if (confirm(&s))       /* doublecheck that all tuples really are covered */
+      report_all(&s);                                  /* report the results */
+    else
+      printf("jenny: internal error, some tuples not covered\n");
+  }
+  cleanup(&s);                                      /* deallocate everything */
+}
+
+int main( int argc, char *argv[])
+{
+  driver(argc, argv);
+  return 0;
+}
+

commit 6f1c63dbda40526817ffb2212367766e1cd63e99
Merge: c444729 294e113
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Jul 16 12:52:01 2018 -0700

    Merge Chris Lam's 'maint-stress-tests' into maint.


commit 294e113fec9ef22e752ad6922971b84f79614f2c
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Mon Jul 16 07:14:44 2018 +0800

    Small typo fix
    
    Fixes typo in a51be5157c383b15a3bddf314b61293cff19c3dd

diff --git a/gnucash/gschemas/org.gnucash.gschema.xml.in b/gnucash/gschemas/org.gnucash.gschema.xml.in
index 6859da3..8d924c0 100644
--- a/gnucash/gschemas/org.gnucash.gschema.xml.in
+++ b/gnucash/gschemas/org.gnucash.gschema.xml.in
@@ -68,7 +68,7 @@
     <key name='force-price-decimal' type="b">
       <default>false</default>
       <summary>Force prices to display as decimals even if they must be rounded.</summary>
-      <description>If active, GnuCash will round prices as necessary to display them as decimals instead of displaying the exact fraction if the fractional part cannont be exactly represented as a decimal.</description>
+      <description>If active, GnuCash will round prices as necessary to display them as decimals instead of displaying the exact fraction if the fractional part cannot be exactly represented as a decimal.</description>
     </key>
     <key name="migrate-prefs-done" type="b">
       <default>false</default>
diff --git a/gnucash/gtkbuilder/dialog-preferences.glade b/gnucash/gtkbuilder/dialog-preferences.glade
index 1514105..58dfc66 100644
--- a/gnucash/gtkbuilder/dialog-preferences.glade
+++ b/gnucash/gtkbuilder/dialog-preferences.glade
@@ -1405,7 +1405,7 @@ many months before the current month:</property>
                     <property name="receives_default">False</property>
                     <property name="has_tooltip">True</property>
                     <property name="tooltip_markup">Force prices to display as decimals even if they must be rounded.</property>
-                    <property name="tooltip_text" translatable="yes">If active, GnuCash will round prices as necessary to display them as decimals instead of displaying the exact fraction if the fractional part cannont be exactly represented as a decimal.</property>
+                    <property name="tooltip_text" translatable="yes">If active, GnuCash will round prices as necessary to display them as decimals instead of displaying the exact fraction if the fractional part cannot be exactly represented as a decimal.</property>
                     <property name="halign">start</property>
                     <property name="margin_left">12</property>
                     <property name="use_underline">True</property>

commit 414ab99aa07c550882ffb0c65f8d415dfb1d7008
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Mon Jul 16 00:18:58 2018 +0800

    [test-stress-options] Set COMBINATORICS to full path to jenny
    
    Setting COMBINATORICS to the full path name to jenny will enable
    pairwise testing.
    
    e.g. COMBINATORICS=/home/user/jenny/jenny ninja check

diff --git a/gnucash/report/standard-reports/test/test-stress-options.scm b/gnucash/report/standard-reports/test/test-stress-options.scm
index da2ffb6..4265273 100644
--- a/gnucash/report/standard-reports/test/test-stress-options.scm
+++ b/gnucash/report/standard-reports/test/test-stress-options.scm
@@ -17,6 +17,15 @@
 (use-modules (sxml simple))
 (use-modules (sxml xpath))
 
+;; NOTE
+;; ----
+;; SIMPLE stress tests by default
+;;
+;; PAIRWISE COMBINATORICS are enabled by setting environment variable COMBINATORICS
+;; to the fullpath for the compiled jenny from http://burtleburtle.net/bob/math/jenny.html
+;;
+;; e.g. COMBINATORICS=/home/user/jenny/jenny ninja check
+
 (load "test-stress-optionslist.scm")
 ;; The above optionslist was generated programmatically.  It was
 ;; generated by running a sentinel function in the middle of an
@@ -81,6 +90,18 @@
   (tests)
   (test-end "stress options"))
 
+(define jennypath
+  (get-environment-variable "COMBINATORICS"))
+
+(define jenny-exists?
+  ;; this is a simple test for presence of jenny - will check
+  ;; COMBINATORICS env exists, and running it produces exit-code of
+  ;; zero, and tests the first few letters of its output.
+  (and (string? jennypath)
+       (zero? (system jennypath))
+       (string=? (string-take (get-string-all (open-input-pipe jennypath)) 6)
+                 "jenny:")))
+
 (define (set-option! options section name value)
   (let ((option (gnc:lookup-option options section name)))
     (if option
@@ -175,8 +196,8 @@
                            ;; the following is the n-tuple
                            2
                            (length report-options)))
-                 (cmdline (format #f "/home/chris/sources/jenny/jenny -n~a ~a"
-                                  n-tuple jennyargs))
+                 (cmdline (format #f "~a -n~a ~a"
+                                  jennypath n-tuple jennyargs))
                  (jennyout (get-string-all (open-input-pipe cmdline)))
                  (test-cases (string-split jennyout #\newline)))
             (for-each
@@ -221,7 +242,7 @@
   ;; what strategy are we using here? simple stress test (ie tests as
   ;; many times as the maximum number of options) or combinatorial
   ;; tests (using jenny)
-  (if (get-environment-variable "COMBINATORICS")
+  (if jenny-exists?
       combinatorial-stress-test
       simple-stress-test))
 

commit b8ce2b545ac83ff6e5d059a1c15c33b4789d024d
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 10 23:33:40 2018 +0800

    [stress-test] run tests on empty book and populated book
    
    Refactor (tests) into call (create-test-data) which will add sample
    txns in the environment.
    
    As a result we can run tests twice - once before and once
    after (create-test-data) which helps dislodge a few more report bugs.

diff --git a/gnucash/report/standard-reports/test/test-stress-options.scm b/gnucash/report/standard-reports/test/test-stress-options.scm
index 028d049..da2ffb6 100644
--- a/gnucash/report/standard-reports/test/test-stress-options.scm
+++ b/gnucash/report/standard-reports/test/test-stress-options.scm
@@ -225,7 +225,7 @@
       combinatorial-stress-test
       simple-stress-test))
 
-(define (tests)
+(define (create-test-data)
   (let* ((env (create-test-env))
          (account-alist (env-create-account-structure-alist env structure))
          (bank (cdr (assoc "Bank" account-alist)))
@@ -280,33 +280,39 @@
               (iota 12))
     (let ((mid (floor (/ (+ (gnc-accounting-period-fiscal-start)
                             (gnc-accounting-period-fiscal-end)) 2))))
-      (env-create-transaction env mid bank income 200))
+      (env-create-transaction env mid bank income 200))))
 
-    (for-each
-     (lambda (option-set)
-       (let ((report-name (assq-ref option-set 'report-name))
-             (report-guid (assq-ref option-set 'report-id))
-             (report-options (assq-ref option-set 'options)))
-         (if (member report-name
-                     ;; these reports seem to cause problems when running...
-                     '("Income Statement"
-                       "Tax Invoice"
-                       "Net Worth Linechart"
-                       "Tax Schedule Report/TXF Export"
-                       "Receipt"
-                       "Future Scheduled Transactions Summary"
-                       "Welcome to GnuCash"
-                       "Hello, World"
-                       "Budget Income Statement"
-                       "Multicolumn View"
-                       "General Journal"
-                       "Australian Tax Invoice"
-                       "Balance Sheet (eguile)"
-                       ;; "Budget Flow"
-                       "networth"
-                       ))
-             (format #t "\nSkipping ~a...\n" report-name)
-             (begin
-               (format #t "\nTesting ~a...\n" report-name)
-               (test report-name report-guid report-options)))))
-     optionslist)))
+(define (run-tests prefix)
+  (for-each
+   (lambda (option-set)
+     (let ((report-name (assq-ref option-set 'report-name))
+           (report-guid (assq-ref option-set 'report-id))
+           (report-options (assq-ref option-set 'options)))
+       (if (member report-name
+                   ;; these reports seem to cause problems when running...
+                   '(
+                     ;; eguile-based reports
+                     "Tax Invoice"
+                     "Receipt"
+                     "Australian Tax Invoice"
+                     "Balance Sheet (eguile)"
+
+                     ;; tax-schedule - locale-dependent?
+                     "Tax Schedule Report/TXF Export"
+
+                     ;; unusual reports
+                     "Welcome to GnuCash"
+                     "Hello, World"
+                     "Multicolumn View"
+                     "General Journal"
+                     ))
+           (format #t "\nSkipping ~a ~a...\n" report-name prefix)
+           (begin
+             (format #t "\nTesting ~a ~a...\n" report-name prefix)
+             (test report-name report-guid report-options)))))
+   optionslist))
+
+(define (tests)
+  (run-tests "with empty book")
+  (create-test-data)
+  (run-tests "on a populated book"))

commit aa4da810c1cc9b00c829ef77bc4ff8a8792c92a2
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 8 14:59:30 2018 +0800

    [test-stress-options] introduce combinatorial testing
    
    This is enabled if the environment variable COMBINATORICS exists.
    I guess it can be run via:
    
    COMBINATORICS=bla ninja check

diff --git a/gnucash/report/standard-reports/test/test-stress-options.scm b/gnucash/report/standard-reports/test/test-stress-options.scm
index 92d6aaf..028d049 100644
--- a/gnucash/report/standard-reports/test/test-stress-options.scm
+++ b/gnucash/report/standard-reports/test/test-stress-options.scm
@@ -1,3 +1,5 @@
+(use-modules (ice-9 textual-ports))
+(use-modules (ice-9 popen))
 (use-modules (gnucash utilities))
 (use-modules (gnucash gnc-module))
 (gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
@@ -10,6 +12,7 @@
 (use-modules (gnucash report report-system))
 (use-modules (gnucash report report-system test test-extras))
 (use-modules (srfi srfi-64))
+(use-modules (srfi srfi-98))
 (use-modules (gnucash engine test srfi64-extras))
 (use-modules (sxml simple))
 (use-modules (sxml xpath))
@@ -103,7 +106,7 @@
         (list "Equity" (list (cons 'type ACCT-TYPE-EQUITY)))
         ))
 
-(define (test report-name uuid report-options)
+(define (simple-stress-test report-name uuid report-options)
   (let ((options (gnc:make-report-options uuid)))
     (test-assert (format #f "basic test ~a" report-name)
       (gnc:options->render uuid options (string-append "stress-" report-name) "test"))
@@ -146,6 +149,82 @@
                   report-options)))
      )))
 
+(define (combinatorial-stress-test report-name uuid report-options)
+  (let* ((options (gnc:make-report-options uuid))
+         (render #f))
+    (test-assert (format #f "basic test ~a" report-name)
+      (set! render
+        (gnc:options->render
+         uuid options (string-append "stress-" report-name) "test")))
+    (if render
+        (begin
+          (format #t "Testing n-tuple combinatorics for:\n~a" report-name)
+          (for-each
+           (lambda (option)
+             (format #t ",~a/~a"
+                     (vector-ref option 0)
+                     (vector-ref option 1)))
+           report-options)
+          (newline)
+          ;; generate combinatorics
+          (let* ((option-lengths (map (lambda (report-option)
+                                        (length (vector-ref report-option 3)))
+                                      report-options))
+                 (jennyargs (string-join (map number->string option-lengths) " "))
+                 (n-tuple (min
+                           ;; the following is the n-tuple
+                           2
+                           (length report-options)))
+                 (cmdline (format #f "/home/chris/sources/jenny/jenny -n~a ~a"
+                                  n-tuple jennyargs))
+                 (jennyout (get-string-all (open-input-pipe cmdline)))
+                 (test-cases (string-split jennyout #\newline)))
+            (for-each
+             (lambda (case)
+               (unless (string-null? case)
+                 (let* ((choices-str (string-filter char-alphabetic? case))
+                        (choices-alpha (map char->integer (string->list choices-str)))
+                        (choices (map (lambda (n)
+                                        (- n (if (> n 96) 97 39))) ; a-z -> 0-25, and A-Z -> 26-51
+                                      choices-alpha)))
+                   (let loop ((option-idx (1- (length report-options)))
+                              (option-summary '()))
+                     (if (negative? option-idx)
+                         (catch #t
+                           (lambda ()
+                             (gnc:options->render uuid options "stress-test" "test")
+                             (format #t "[pass] ~a:~a \n"
+                                     report-name
+                                     (string-join option-summary ",")))
+                           (lambda (k . args)
+                             (format #t "[fail]... error (~s . ~s) options-list are:\n~a"
+                                     k args
+                                     (gnc:html-render-options-changed options #t))
+                             (test-assert "logging test failure as above..."
+                               #f)))
+                         (let* ((option (list-ref report-options option-idx))
+                                (section (vector-ref option 0))
+                                (name (vector-ref option 1))
+                                (value (list-ref (vector-ref option 3)
+                                                 (list-ref choices option-idx))))
+                           (set-option! options section name value)
+                           (loop (1- option-idx)
+                                 (cons (format #f "~a"
+                                               (cond
+                                                ((boolean? value) (if value 't 'f))
+                                                (else value)))
+                                       option-summary))))))))
+             test-cases)))
+        (display "...aborted due to basic test failure"))))
+
+(define test
+  ;; what strategy are we using here? simple stress test (ie tests as
+  ;; many times as the maximum number of options) or combinatorial
+  ;; tests (using jenny)
+  (if (get-environment-variable "COMBINATORICS")
+      combinatorial-stress-test
+      simple-stress-test))
+
 (define (tests)
   (let* ((env (create-test-env))
          (account-alist (env-create-account-structure-alist env structure))
@@ -223,8 +302,11 @@
                        "General Journal"
                        "Australian Tax Invoice"
                        "Balance Sheet (eguile)"
+                       ;; "Budget Flow"
                        "networth"
                        ))
-             (format #t "Skipping ~a...\n" report-name)
-           (test report-name report-guid report-options))))
+             (format #t "\nSkipping ~a...\n" report-name)
+             (begin
+               (format #t "\nTesting ~a...\n" report-name)
+               (test report-name report-guid report-options)))))
      optionslist)))

commit dfe1f34573fcae11b8447f4f2c83d5418d9eac53
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Sun Jul 1 14:12:39 2018 +0800

    [stress-test] stress test options!

diff --git a/gnucash/report/standard-reports/test/CMakeLists.txt b/gnucash/report/standard-reports/test/CMakeLists.txt
index 0eed778..ed24291 100644
--- a/gnucash/report/standard-reports/test/CMakeLists.txt
+++ b/gnucash/report/standard-reports/test/CMakeLists.txt
@@ -10,6 +10,7 @@ set(scm_test_with_srfi64_SOURCES
   test-charts.scm
   test-transaction.scm
   test-balance-sheet.scm
+  test-stress-options.scm
   test-income-gst.scm
 )
 
diff --git a/gnucash/report/standard-reports/test/test-stress-options.scm b/gnucash/report/standard-reports/test/test-stress-options.scm
new file mode 100644
index 0000000..92d6aaf
--- /dev/null
+++ b/gnucash/report/standard-reports/test/test-stress-options.scm
@@ -0,0 +1,230 @@
+(use-modules (gnucash utilities))
+(use-modules (gnucash gnc-module))
+(gnc:module-begin-syntax (gnc:module-load "gnucash/app-utils" 0))
+(use-modules (gnucash engine test test-extras))
+(use-modules (gnucash report standard-reports))
+(use-modules (gnucash report business-reports))
+(use-modules (gnucash report view-column))
+(use-modules (gnucash report stylesheets))
+(use-modules (gnucash report taxinvoice))
+(use-modules (gnucash report report-system))
+(use-modules (gnucash report report-system test test-extras))
+(use-modules (srfi srfi-64))
+(use-modules (gnucash engine test srfi64-extras))
+(use-modules (sxml simple))
+(use-modules (sxml xpath))
+
+(load "test-stress-optionslist.scm")
+;; The above optionslist was generated programmatically.  It was
+;; generated by running a sentinel function in the middle of an
+;; existing report renderer. The sentinel function is defined as
+;; follows, then was cleaned up using emacs' indenting. No other
+;; processing was done.  Only the multichoice and boolean options for
+;; most reports were dumped.  Although this cannot provide whole
+;; coverage for option permutations, it can catch many errors while
+;; refactoring inner functions.
+
+;; (define (mydump)
+;;   (with-output-to-file "test-stress-optionslist.scm"
+;;     (lambda ()
+;;       (display "(define optionslist\n (list \n")
+;;       (gnc:report-templates-for-each
+;;        (lambda (report-id template)
+;;          (let* ((options-generator (gnc:report-template-options-generator template))
+;;                 (name (gnc:report-template-name template))
+;;                 (options (and (not (string=? name "General Journal"))
+;;                               (options-generator))))
+;;            (define (disp d)
+;;              (define (try proc)
+;;                (catch 'wrong-type-arg
+;;                  (lambda () (proc d))
+;;                  (const #f)))
+;;              (or (and (symbol? d) (string-append "'" (symbol->string d)))
+;;                  (and (list? d) (string-append "(list " (string-join (map disp d) " ") ")"))
+;;                  (and (pair? d) (format #f "(cons ~a . ~a)"
+;;                                         (disp (car d))
+;;                                         (disp (cdr d))))
+;;                  (try gnc-commodity-get-mnemonic)
+;;                  (try xaccAccountGetName)
+;;                  (try gnc-budget-get-name)
+;;                  (format #f "~s" d)))
+;;            (format #t "(list (cons 'report-id ~s)\n      (cons 'report-name ~s)\n      (cons 'options\n (list\n"
+;;                    report-id (gnc:report-template-name template))
+;;            (if options
+;;                (gnc:options-for-each
+;;                 (lambda (option)
+;;                   (if (memq (gnc:option-type option) '(multichoice boolean))
+;;                       (format #t "                  (vector ~s ~s '~s '~s)\n"
+;;                               (gnc:option-section option)
+;;                               (gnc:option-name option)
+;;                               (gnc:option-type option)
+;;                               (case (gnc:option-type option)
+;;                                 ((multichoice) (map (lambda (d) (vector-ref d 0)) (gnc:option-data option)))
+;;                                 ((boolean) (list #t #f))
+;;                                 ;; (else "\"\"")
+;;                                 (else (disp (gnc:option-value option)))))))
+;;                 options)
+;;                )
+;;            (display ")))\n"))))
+;;       (display "))\n"))))
+
+
+;; Explicitly set locale to make the report output predictable
+(setlocale LC_ALL "C")
+
+(define (run-test)
+  (test-runner-factory gnc:test-runner)
+  (test-begin "stress options")
+  (tests)
+  (test-end "stress options"))
+
+(define (set-option! options section name value)
+  (let ((option (gnc:lookup-option options section name)))
+    (if option
+        (gnc:option-set-value option value))))
+
+(define (mnemonic->commodity sym)
+  (gnc-commodity-table-lookup
+   (gnc-commodity-table-get-table (gnc-get-current-book))
+   (gnc-commodity-get-namespace (gnc-default-report-currency))
+   sym))
+
+(define structure
+  (list "Root" (list (cons 'type ACCT-TYPE-ASSET))
+        (list "Asset"
+              (list "Bank")
+              (list "GBP Bank" (list (cons 'commodity (mnemonic->commodity "GBP"))))
+              (list "Wallet"))
+        (list "Income" (list (cons 'type ACCT-TYPE-INCOME)))
+        (list "Income-GBP" (list (cons 'type ACCT-TYPE-INCOME)
+                                 (cons 'commodity (mnemonic->commodity "GBP"))))
+        (list "Expenses" (list (cons 'type ACCT-TYPE-EXPENSE)))
+        (list "Liabilities" (list (cons 'type ACCT-TYPE-LIABILITY)))
+        (list "Equity" (list (cons 'type ACCT-TYPE-EQUITY)))
+        ))
+
+(define (test report-name uuid report-options)
+  (let ((options (gnc:make-report-options uuid)))
+    (test-assert (format #f "basic test ~a" report-name)
+      (gnc:options->render uuid options (string-append "stress-" report-name) "test"))
+    (format #t "Testing SIMPLE combinations for:\n~a" report-name)
+    (for-each
+     (lambda (option)
+       (format #t ",~a/~a"
+               (vector-ref option 0)
+               (vector-ref option 1)))
+     report-options)
+    (newline)
+    (for-each
+     (lambda (idx)
+       (display report-name)
+       (for-each
+        (lambda (option)
+          (let* ((section (vector-ref option 0))
+                 (name (vector-ref option 1))
+                 (value (list-ref (vector-ref option 3)
+                                  (modulo idx (length (vector-ref option 3))))))
+            (set-option! options section name value)
+            (format #t ",~a"
+                    (cond
+                     ((boolean? value) (if value 't 'f))
+                     (else value)))))
+        report-options)
+       (catch #t
+         (lambda ()
+           (gnc:options->render uuid options "stress-test" "test")
+           (display "[pass]\n"))
+         (lambda (k . args)
+           (format #t "[fail]... error: (~s . ~s) options-list are:\n~a"
+                   k args
+                   (gnc:html-render-options-changed options #t))
+           (test-assert "logging test failure as above..."
+             #f))))
+     (iota
+      (apply max
+             (map (lambda (opt) (length (vector-ref opt 3)))
+                  report-options)))
+     )))
+
+(define (tests)
+  (let* ((env (create-test-env))
+         (account-alist (env-create-account-structure-alist env structure))
+         (bank (cdr (assoc "Bank" account-alist)))
+         (gbp-bank (cdr (assoc "GBP Bank" account-alist)))
+         (wallet (cdr (assoc "Wallet" account-alist)))
+         (income (cdr (assoc "Income" account-alist)))
+         (gbp-income (cdr (assoc "Income-GBP" account-alist)))
+         (expense (cdr (assoc "Expenses" account-alist)))
+         (liability (cdr (assoc "Liabilities" account-alist)))
+         (equity (cdr (assoc "Equity" account-alist))))
+    ;; populate datafile with old transactions
+    (env-transfer env 01 01 1970 bank expense       5   #:description "desc-1" #:num "trn1" #:memo "memo-3")
+    (env-transfer env 31 12 1969 income bank       10   #:description "desc-2" #:num "trn2" #:void-reason "void" #:notes "notes3")
+    (env-transfer env 31 12 1969 income bank       29   #:description "desc-3" #:num "trn3"
+                  #:reconcile (cons #\c (gnc-dmy2time64 01 03 1970)))
+    (env-transfer env 01 02 1970 bank expense      15   #:description "desc-4" #:num "trn4" #:notes "notes2" #:memo "memo-1")
+    (env-transfer env 10 01 1970 liability expense 10   #:description "desc-5" #:num "trn5" #:void-reason "any")
+    (env-transfer env 10 01 1970 liability expense 11   #:description "desc-6" #:num "trn6" #:notes "notes1")
+    (env-transfer env 10 02 1970 bank liability     8   #:description "desc-7" #:num "trn7" #:notes "notes1" #:memo "memo-2"
+                  #:reconcile (cons #\y (gnc-dmy2time64 01 03 1970)))
+    (let ((txn (xaccMallocTransaction (gnc-get-current-book)))
+          (split-1 (xaccMallocSplit  (gnc-get-current-book)))
+          (split-2 (xaccMallocSplit  (gnc-get-current-book)))
+          (split-3 (xaccMallocSplit  (gnc-get-current-book))))
+      (xaccTransBeginEdit txn)
+      (xaccTransSetDescription txn "$100bank -> $80expenses + $20wallet")
+      (xaccTransSetCurrency txn (xaccAccountGetCommodity bank))
+      (xaccTransSetDate txn 14 02 1971)
+      (xaccSplitSetParent split-1 txn)
+      (xaccSplitSetParent split-2 txn)
+      (xaccSplitSetParent split-3 txn)
+      (xaccSplitSetAccount split-1 bank)
+      (xaccSplitSetAccount split-2 expense)
+      (xaccSplitSetAccount split-3 wallet)
+      (xaccSplitSetValue split-1 -100)
+      (xaccSplitSetValue split-2 80)
+      (xaccSplitSetValue split-3 20)
+      (xaccSplitSetAmount split-1 -100)
+      (xaccSplitSetAmount split-2 80)
+      (xaccSplitSetAmount split-3 20)
+      (xaccTransSetNotes txn "multisplit")
+      (xaccTransCommitEdit txn))
+    (let ((closing-txn (env-transfer env 31 12 1977 expense equity 111 #:description "Closing")))
+      (xaccTransSetIsClosingTxn closing-txn #t))
+    (env-transfer-foreign env 15 01 2000 gbp-bank bank 10 14 #:description "GBP 10 to USD 14")
+    (env-transfer-foreign env 15 02 2000 bank gbp-bank  9  6 #:description "USD 9 to GBP 6")
+    (for-each (lambda (m)
+                (env-transfer env 08 (1+ m) 1978 gbp-income gbp-bank 51 #:description "#51 income")
+                (env-transfer env 03 (1+ m) 1978 income bank  103 #:description "$103 income")
+                (env-transfer env 15 (1+ m) 1978 bank expense  22 #:description "$22 expense")
+                (env-transfer env 09 (1+ m) 1978 income bank  109 #:description "$109 income"))
+              (iota 12))
+    (let ((mid (floor (/ (+ (gnc-accounting-period-fiscal-start)
+                            (gnc-accounting-period-fiscal-end)) 2))))
+      (env-create-transaction env mid bank income 200))
+
+    (for-each
+     (lambda (option-set)
+       (let ((report-name (assq-ref option-set 'report-name))
+             (report-guid (assq-ref option-set 'report-id))
+             (report-options (assq-ref option-set 'options)))
+         (if (member report-name
+                     ;; these reports seem to cause problems when running...
+                     '("Income Statement"
+                       "Tax Invoice"
+                       "Net Worth Linechart"
+                       "Tax Schedule Report/TXF Export"
+                       "Receipt"
+                       "Future Scheduled Transactions Summary"
+                       "Welcome to GnuCash"
+                       "Hello, World"
+                       "Budget Income Statement"
+                       "Multicolumn View"
+                       "General Journal"
+                       "Australian Tax Invoice"
+                       "Balance Sheet (eguile)"
+                       "networth"
+                       ))
+             (format #t "Skipping ~a...\n" report-name)
+           (test report-name report-guid report-options))))
+     optionslist)))
diff --git a/gnucash/report/standard-reports/test/test-stress-optionslist.scm b/gnucash/report/standard-reports/test/test-stress-optionslist.scm
new file mode 100644
index 0000000..b9c0220
--- /dev/null
+++ b/gnucash/report/standard-reports/test/test-stress-optionslist.scm
@@ -0,0 +1,972 @@
+(define optionslist
+  (list
+   (list (cons 'report-id "e9cf815f79db44bcb637d0295093ae3d")
+         (cons 'report-name "Assets Over Time")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Chart Type" 'multichoice '(barchart linechart))
+                (vector "Display" "Use Stacked Charts" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show long account names" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "e45218c6d76f11e7b5ef0800277ef320")
+         (cons 'report-name "Reconciliation Report")
+         (cons 'options
+               (list
+                (vector "Accounts" "Filter Type" 'multichoice '(none include exclude))
+                (vector "Display" "Sign Reverses" 'multichoice '(global none income-expense credit-accounts))
+                (vector "Display" "Description" 'boolean '(#t #f))
+                (vector "Display" "Amount" 'multichoice '(none single double))
+                (vector "Display" "Num" 'boolean '(#t #f))
+                (vector "Display" "Shares" 'boolean '(#t #f))
+                (vector "Display" "Use Full Account Name" 'boolean '(#t #f))
+                (vector "Display" "Use Full Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Detail Level" 'multichoice '(multi-line single))
+                (vector "Display" "Account Code" 'boolean '(#t #f))
+                (vector "Display" "Memo" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Account Name" 'boolean '(#t #f))
+                (vector "Display" "Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Price" 'boolean '(#t #f))
+                (vector "Display" "Other Account Code" 'boolean '(#t #f))
+                (vector "Display" "Reconciled Date" 'boolean '(#t #f))
+                (vector "Display" "Subtotal Table" 'boolean '(#t #f))
+                (vector "Display" "Notes" 'boolean '(#t #f))
+                (vector "Display" "Date" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show Full Account Name" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Code" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Description" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Secondary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Add indenting columns" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Primary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Show Informal Debit/Credit Headers" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show subtotals only (hide transactional data)" 'boolean '(#t #f))
+                (vector "Filter" "Closing transactions" 'multichoice '(exclude-closing include-both closing-only))
+                (vector "Filter" "Void Transactions" 'multichoice '(non-void-only void-only both))
+                (vector "Filter" "Use regular expressions for transaction filter" 'boolean '(#t #f))
+                (vector "Filter" "Use regular expressions for account name filter" 'boolean '(#t #f))
+                (vector "Filter" "Reconcile Status" 'multichoice '(all unreconciled cleared reconciled))
+                (vector "General" "Show original currency amount" 'boolean '(#t #f))
+                (vector "General" "Table for Exporting" 'boolean '(#t #f))
+                (vector "General" "Add options summary" 'multichoice '(no-match always never))
+                (vector "General" "Common Currency" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "583c313fcc484efc974c4c844404f454")
+         (cons 'report-name "Budget Income Statement")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Display" "Display as a two column report" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Display in standard, income first, order" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Label the revenue section" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Include expense total" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Include revenue total" 'boolean '(#t #f))
+                (vector "Display" "Label the expense section" 'boolean '(#t #f))
+                (vector "General" "Report for range of budget periods" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "4166a20981985fd2b07ff8cb3b7d384e")
+         (cons 'report-name "Customer Summary")
+         (cons 'options
+               (list
+                (vector "Display" "Sort Order" 'multichoice '(ascend descend))
+                (vector "Display" "Show Inactive Customers" 'boolean '(#t #f))
+                (vector "Display" "Show Lines with All Zeros" 'boolean '(#t #f))
+                (vector "Display" "Show Expense Column" 'boolean '(#t #f))
+                (vector "Display" "Sort Column" 'multichoice '(customername profit markup sales expense))
+                (vector "Display" "Show Company Address" 'boolean '(#t #f))
+                (vector "__reg" "reverse?" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "d8ba4a2e89e8479ca9f6eccdeb164588")
+         (cons 'report-name "Multicolumn View")
+         (cons 'options
+               (list
+                )))
+   (list (cons 'report-id "216cd0cf6931453ebcce85415aba7082")
+         (cons 'report-name "Trial Balance")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Entries" "Adjusting Entries Pattern is regular expression" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries pattern is case-sensitive" 'boolean '(#t #f))
+                (vector "Entries" "Adjusting Entries pattern is case-sensitive" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries Pattern is regular expression" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "General" "Report variation" 'multichoice '(current pre-adj work-sheet))
+                )))
+   (list (cons 'report-id "47f45d7d6d57b68518481c1fc8d4e4ba")
+         (cons 'report-name "Future Scheduled Transactions Summary")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Depth limit behavior" 'multichoice '(summarize flatten truncate))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Account Description" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Account Code" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Account Notes" 'boolean '(#t #f))
+                (vector "Display" "Account Type" 'boolean '(#t #f))
+                (vector "Display" "Account Balance" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "c2a996c8970f43448654ca84f17dda24")
+         (cons 'report-name "Equity Statement")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries pattern is case-sensitive" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries Pattern is regular expression" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "3dbbc2584da64e7a8674355bc3fbfe3d")
+         (cons 'report-name "Australian Tax Invoice")
+         (cons 'options
+               (list
+                (vector "Display" "table-border-collapse" 'boolean '(#t #f))
+                (vector "Elements" "Show net price" 'boolean '(#t #f))
+                (vector "Elements" "column: Units" 'boolean '(#t #f))
+                (vector "Elements" "Show Job number" 'boolean '(#t #f))
+                (vector "Elements" "row: Address" 'boolean '(#t #f))
+                (vector "Elements" "column: Date" 'boolean '(#t #f))
+                (vector "Elements" "column: Tax Rate" 'boolean '(#t #f))
+                (vector "Elements" "Invoice number next to title" 'boolean '(#t #f))
+                (vector "Elements" "row: Company Name" 'boolean '(#t #f))
+                (vector "Elements" "row: Invoice Number" 'boolean '(#t #f))
+                (vector "Elements" "Show Job name" 'boolean '(#t #f))
+                (vector "Elements" "row: Contact" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "2e3751edeb7544e8a20fd19e9d08bb65")
+         (cons 'report-name "Balance Sheet (eguile)")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Accounts" "Exclude accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Negative amount format" 'multichoice '(negsign negbrackets))
+                (vector "Display" "1- or 2-column report" 'multichoice '(autocols onecol twocols))
+                )))
+   (list (cons 'report-id "e9418ff64f2c11e5b61d1c7508d793ed")
+         (cons 'report-name "Securities")
+         (cons 'options
+               (list
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "Display" "Show long names" 'boolean '(#t #f))
+                (vector "Display" "Show Percents" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "e1bd09b8a1dd49dd85760db9d82b045c")
+         (cons 'report-name "Income Accounts")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "Display" "Show long names" 'boolean '(#t #f))
+                (vector "Display" "Show Percents" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "General" "Show Average" 'multichoice '(None YearDelta MonthDelta WeekDelta))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "2e22929e5c5b4b769f615a815ef0c20f")
+         (cons 'report-name "General Ledger")
+         (cons 'options
+               (list
+                (vector "Accounts" "Filter Type" 'multichoice '(none include exclude))
+                (vector "Display" "Sign Reverses" 'multichoice '(global none income-expense credit-accounts))
+                (vector "Display" "Description" 'boolean '(#t #f))
+                (vector "Display" "Amount" 'multichoice '(none single double))
+                (vector "Display" "Num" 'boolean '(#t #f))
+                (vector "Display" "Running Balance" 'boolean '(#t #f))
+                (vector "Display" "Shares" 'boolean '(#t #f))
+                (vector "Display" "Use Full Account Name" 'boolean '(#t #f))
+                (vector "Display" "Use Full Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Detail Level" 'multichoice '(multi-line single))
+                (vector "Display" "Account Code" 'boolean '(#t #f))
+                (vector "Display" "Memo" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Account Name" 'boolean '(#t #f))
+                (vector "Display" "Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Price" 'boolean '(#t #f))
+                (vector "Display" "Other Account Code" 'boolean '(#t #f))
+                (vector "Display" "Reconciled Date" 'boolean '(#t #f))
+                (vector "Display" "Subtotal Table" 'boolean '(#t #f))
+                (vector "Display" "Notes" 'boolean '(#t #f))
+                (vector "Display" "Date" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show Full Account Name" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Code" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Description" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Secondary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Add indenting columns" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Primary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Show Informal Debit/Credit Headers" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show subtotals only (hide transactional data)" 'boolean '(#t #f))
+                (vector "Filter" "Closing transactions" 'multichoice '(exclude-closing include-both closing-only))
+                (vector "Filter" "Void Transactions" 'multichoice '(non-void-only void-only both))
+                (vector "Filter" "Use regular expressions for transaction filter" 'boolean '(#t #f))
+                (vector "Filter" "Use regular expressions for account name filter" 'boolean '(#t #f))
+                (vector "Filter" "Reconcile Status" 'multichoice '(all unreconciled cleared reconciled))
+                (vector "General" "Stylesheet" 'multichoice '(Default Easy Footer #{Head or Tail}# Technicolor))
+                (vector "General" "Show original currency amount" 'boolean '(#t #f))
+                (vector "General" "Table for Exporting" 'boolean '(#t #f))
+                (vector "General" "Add options summary" 'multichoice '(no-match always never))
+                (vector "General" "Common Currency" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "9cf76bed17f14401b8e3e22d0079cb98")
+         (cons 'report-name "Receivable Aging")
+         (cons 'options
+               (list
+                (vector "Display" "Address Name" 'boolean '(#t #f))
+                (vector "Display" "Address 1" 'boolean '(#t #f))
+                (vector "Display" "Active" 'boolean '(#t #f))
+                (vector "Display" "Address 2" 'boolean '(#t #f))
+                (vector "Display" "Address 3" 'boolean '(#t #f))
+                (vector "Display" "Address Source" 'multichoice '(billing shipping))
+                (vector "Display" "Address Email" 'boolean '(#t #f))
+                (vector "Display" "Address Phone" 'boolean '(#t #f))
+                (vector "Display" "Address 4" 'boolean '(#t #f))
+                (vector "Display" "Address Fax" 'boolean '(#t #f))
+                (vector "General" "Sort By" 'multichoice '(name total oldest-bracket))
+                (vector "General" "Show zero balance items" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Sort Order" 'multichoice '(increasing decreasing))
+                (vector "General" "Due or Post Date" 'multichoice '(duedate postdate))
+                (vector "General" "Show Multi-currency Totals" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "3ce293441e894423a2425d7a22dd1ac6")
+         (cons 'report-name "Fancy Invoice")
+         (cons 'options
+               (list
+                (vector "Display" "Payable to" 'boolean '(#t #f))
+                (vector "Display" "Individual Taxes" 'boolean '(#t #f))
+                (vector "Display" "Billing Terms" 'boolean '(#t #f))
+                (vector "Display" "References" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Invoice Notes" 'boolean '(#t #f))
+                (vector "Display" "Billing ID" 'boolean '(#t #f))
+                (vector "Display" "Payments" 'boolean '(#t #f))
+                (vector "Display" "Company contact" 'boolean '(#t #f))
+                (vector "Display Columns" "Tax Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Discount" 'boolean '(#t #f))
+                (vector "Display Columns" "Total" 'boolean '(#t #f))
+                (vector "Display Columns" "Action" 'boolean '(#t #f))
+                (vector "Display Columns" "Taxable" 'boolean '(#t #f))
+                (vector "Display Columns" "Quantity" 'boolean '(#t #f))
+                (vector "Display Columns" "Price" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "08ae9c2e884b4f9787144f47eacd7f44")
+         (cons 'report-name "Employee Report")
+         (cons 'options
+               (list
+                (vector "Display Columns" "Tax" 'boolean '(#t #f))
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Debits" 'boolean '(#t #f))
+                (vector "Display Columns" "Type" 'boolean '(#t #f))
+                (vector "Display Columns" "Due Date" 'boolean '(#t #f))
+                (vector "Display Columns" "Sale" 'boolean '(#t #f))
+                (vector "Display Columns" "Reference" 'boolean '(#t #f))
+                (vector "Display Columns" "Credits" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                (vector "General" "Due or Post Date" 'multichoice '(duedate postdate))
+                (vector "__reg" "reverse?" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "8758ba23984c40dea5527f5f0ca2779e")
+         (cons 'report-name "Profit & Loss")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries pattern is case-sensitive" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries Pattern is regular expression" 'boolean '(#t #f))
+                (vector "Display" "Display as a two column report" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Display in standard, income first, order" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Label the revenue section" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Include expense total" 'boolean '(#t #f))
+                (vector "Display" "Include trading accounts total" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Include revenue total" 'boolean '(#t #f))
+                (vector "Display" "Label the expense section" 'boolean '(#t #f))
+                (vector "Display" "Label the trading accounts section" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "c4173ac99b2b448289bf4d11c731af13")
+         (cons 'report-name "Balance Sheet")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Include assets total" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Include liabilities total" 'boolean '(#t #f))
+                (vector "Display" "Label the liabilities section" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Label the assets section" 'boolean '(#t #f))
+                (vector "Display" "Include equity total" 'boolean '(#t #f))
+                (vector "Display" "Label the equity section" 'boolean '(#t #f))
+                (vector "General" "Single column Balance Sheet" 'boolean '(#t #f))
+                (vector "General" "Use standard US layout" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "c146317be32e4948a561ec7fc89d15c1")
+         (cons 'report-name "Customer Report")
+         (cons 'options
+               (list
+                (vector "Display Columns" "Tax" 'boolean '(#t #f))
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Debits" 'boolean '(#t #f))
+                (vector "Display Columns" "Type" 'boolean '(#t #f))
+                (vector "Display Columns" "Due Date" 'boolean '(#t #f))
+                (vector "Display Columns" "Sale" 'boolean '(#t #f))
+                (vector "Display Columns" "Reference" 'boolean '(#t #f))
+                (vector "Display Columns" "Credits" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                (vector "General" "Due or Post Date" 'multichoice '(duedate postdate))
+                (vector "__reg" "reverse?" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "2fe3b9833af044abb929a88d5a59620f")
+         (cons 'report-name "Transaction Report")
+         (cons 'options
+               (list
+                (vector "Accounts" "Filter Type" 'multichoice '(none include exclude))
+                (vector "Display" "Sign Reverses" 'multichoice '(global none income-expense credit-accounts))
+                (vector "Display" "Description" 'boolean '(#t #f))
+                (vector "Display" "Amount" 'multichoice '(none single double))
+                (vector "Display" "Num" 'boolean '(#t #f))
+                (vector "Display" "Running Balance" 'boolean '(#t #f))
+                (vector "Display" "Shares" 'boolean '(#t #f))
+                (vector "Display" "Use Full Account Name" 'boolean '(#t #f))
+                (vector "Display" "Use Full Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Detail Level" 'multichoice '(multi-line single))
+                (vector "Display" "Account Code" 'boolean '(#t #f))
+                (vector "Display" "Memo" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Account Name" 'boolean '(#t #f))
+                (vector "Display" "Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Price" 'boolean '(#t #f))
+                (vector "Display" "Other Account Code" 'boolean '(#t #f))
+                (vector "Display" "Reconciled Date" 'boolean '(#t #f))
+                (vector "Display" "Subtotal Table" 'boolean '(#t #f))
+                (vector "Display" "Notes" 'boolean '(#t #f))
+                (vector "Display" "Date" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show Full Account Name" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Code" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Description" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Secondary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Add indenting columns" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Primary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Show Informal Debit/Credit Headers" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show subtotals only (hide transactional data)" 'boolean '(#t #f))
+                (vector "Filter" "Closing transactions" 'multichoice '(exclude-closing include-both closing-only))
+                (vector "Filter" "Void Transactions" 'multichoice '(non-void-only void-only both))
+                (vector "Filter" "Use regular expressions for transaction filter" 'boolean '(#t #f))
+                (vector "Filter" "Use regular expressions for account name filter" 'boolean '(#t #f))
+                (vector "Filter" "Reconcile Status" 'multichoice '(all unreconciled cleared reconciled))
+                (vector "General" "Show original currency amount" 'boolean '(#t #f))
+                (vector "General" "Table for Exporting" 'boolean '(#t #f))
+                (vector "General" "Add options summary" 'multichoice '(no-match always never))
+                (vector "General" "Common Currency" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "e57770f2dbca46619d6dac4ac5469b50")
+         (cons 'report-name "Payable Aging")
+         (cons 'options
+               (list
+                (vector "Display" "Address Name" 'boolean '(#t #f))
+                (vector "Display" "Address 1" 'boolean '(#t #f))
+                (vector "Display" "Active" 'boolean '(#t #f))
+                (vector "Display" "Address 2" 'boolean '(#t #f))
+                (vector "Display" "Address 3" 'boolean '(#t #f))
+                (vector "Display" "Address Email" 'boolean '(#t #f))
+                (vector "Display" "Address Phone" 'boolean '(#t #f))
+                (vector "Display" "Address 4" 'boolean '(#t #f))
+                (vector "Display" "Address Fax" 'boolean '(#t #f))
+                (vector "General" "Sort By" 'multichoice '(name total oldest-bracket))
+                (vector "General" "Show zero balance items" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Sort Order" 'multichoice '(increasing decreasing))
+                (vector "General" "Due or Post Date" 'multichoice '(duedate postdate))
+                (vector "General" "Show Multi-currency Totals" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "5426e4d987f6444387fe70880e5b28a0")
+         (cons 'report-name "Cash Flow Barchart")
+         (cons 'options
+               (list
+                (vector "Accounts" "Include Trading Accounts in report" 'boolean '(#t #f))
+                (vector "Display" "Show Table" 'boolean '(#t #f))
+                (vector "Display" "Show Money In" 'boolean '(#t #f))
+                (vector "Display" "Show Money Out" 'boolean '(#t #f))
+                (vector "Display" "Show Net Flow" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "0769e242be474010b4acf264a5512e6e")
+         (cons 'report-name "Tax Invoice")
+         (cons 'options
+               (list
+                (vector "Display" "table-border-collapse" 'boolean '(#t #f))
+                (vector "Elements" "Show net price" 'boolean '(#t #f))
+                (vector "Elements" "column: Units" 'boolean '(#t #f))
+                (vector "Elements" "Show Job number" 'boolean '(#t #f))
+                (vector "Elements" "row: Address" 'boolean '(#t #f))
+                (vector "Elements" "column: Date" 'boolean '(#t #f))
+                (vector "Elements" "column: Tax Rate" 'boolean '(#t #f))
+                (vector "Elements" "Invoice number next to title" 'boolean '(#t #f))
+                (vector "Elements" "row: Company Name" 'boolean '(#t #f))
+                (vector "Elements" "row: Invoice Number" 'boolean '(#t #f))
+                (vector "Elements" "Show Job name" 'boolean '(#t #f))
+                (vector "Elements" "row: Contact" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "dde49fed4ca940959ae7d01b72742530")
+         (cons 'report-name "Expenses vs. Day of Week")
+         (cons 'options
+               (list
+                (vector "Accounts" "Include Sub-Accounts" 'boolean '(#t #f))
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "d5adcc61c62e4b8684dd8907448d7900")
+         (cons 'report-name "Average Balance")
+         (cons 'options
+               (list
+                (vector "Accounts" "Include Sub-Accounts" 'boolean '(#t #f))
+                (vector "Accounts" "Exclude transactions between selected accounts" 'boolean '(#t #f))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show plot" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "25455562bd234dd0b048ecc5a8af9e43")
+         (cons 'report-name "General Journal")
+         (cons 'options
+               (list
+                )))
+   (list (cons 'report-id "67112f318bef4fc496bdc27d106bbda4")
+         (cons 'report-name "Easy Invoice")
+         (cons 'options
+               (list
+                (vector "Display" "My Company" 'boolean '(#t #f))
+                (vector "Display" "Subtotal" 'boolean '(#t #f))
+                (vector "Display" "Individual Taxes" 'boolean '(#t #f))
+                (vector "Display" "Billing Terms" 'boolean '(#t #f))
+                (vector "Display" "Due Date" 'boolean '(#t #f))
+                (vector "Display" "References" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "My Company ID" 'boolean '(#t #f))
+                (vector "Display" "Invoice Notes" 'boolean '(#t #f))
+                (vector "Display" "Billing ID" 'boolean '(#t #f))
+                (vector "Display" "Payments" 'boolean '(#t #f))
+                (vector "Display Columns" "Tax Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Discount" 'boolean '(#t #f))
+                (vector "Display Columns" "Total" 'boolean '(#t #f))
+                (vector "Display Columns" "Charge Type" 'boolean '(#t #f))
+                (vector "Display Columns" "Taxable" 'boolean '(#t #f))
+                (vector "Display Columns" "Quantity" 'boolean '(#t #f))
+                (vector "Display Columns" "Price" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "3298541c236b494998b236dfad6ad752")
+         (cons 'report-name "Account Summary")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Depth limit behavior" 'multichoice '(summarize flatten truncate))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Account Description" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Account Code" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Account Notes" 'boolean '(#t #f))
+                (vector "Display" "Account Type" 'boolean '(#t #f))
+                (vector "Display" "Account Balance" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "80769921e87943adade887b9835a7685")
+         (cons 'report-name "Income/Expense Chart")
+         (cons 'options
+               (list
+                (vector "Display" "Show Income/Expense" 'boolean '(#t #f))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show Net Profit" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "f8921f4e5c284d7caca81e239f468a68")
+         (cons 'report-name "Tax Schedule Report/TXF Export")
+         (cons 'options
+               (list
+                (vector "Display" "Do not print transaction detail" 'boolean '(#t #f))
+                (vector "Display" "Do not print Action:Memo data" 'boolean '(#t #f))
+                (vector "Display" "Currency conversion date" 'multichoice '(conv-to-tran-date conv-to-report-date))
+                (vector "Display" "Print TXF export parameters" 'boolean '(#t #f))
+                (vector "Display" "Print all Transfer To/From Accounts" 'boolean '(#t #f))
+                (vector "Display" "Suppress $0.00 values" 'boolean '(#t #f))
+                (vector "Display" "Do not use special date processing" 'boolean '(#t #f))
+                (vector "Display" "Do not print full account names" 'boolean '(#t #f))
+                (vector "General" "Alternate Period" 'multichoice '(from-to #{1st-est}# #{2nd-est}# #{3rd-est}# #{4th-est}# last-year #{1st-last}# #{2nd-last}# #{3rd-last}# #{4th-last}#))
+                )))
+   (list (cons 'report-id "810ed4b25ef0486ea43bbd3dddb32b11")
+         (cons 'report-name "Budget Report")
+         (cons 'options
+               (list
+                (vector "Accounts" "Always show sub-accounts" 'boolean '(#t #f))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Accounts" "Account Display Depth" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Actual" 'boolean '(#t #f))
+                (vector "Display" "Show Budget" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances and budget values" 'boolean '(#t #f))
+                (vector "Display" "Roll up budget amounts to parent" 'boolean '(#t #f))
+                (vector "Display" "Show Column with Totals" 'boolean '(#t #f))
+                (vector "Display" "Show Difference" 'boolean '(#t #f))
+                (vector "General" "Report for range of budget periods" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Range end" 'multichoice '(first previous current next last manual))
+                (vector "General" "Include collapsed periods before selected." 'boolean '(#t #f))
+                (vector "General" "Range start" 'multichoice '(first previous current next last manual))
+                (vector "General" "Include collapsed periods after selected." 'boolean '(#t #f))
+                (vector "General" "Show Full Account Names" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "cbba1696c8c24744848062c7f1cf4a72")
+         (cons 'report-name "Net Worth Barchart")
+         (cons 'options
+               (list
+                (vector "Display" "Show Asset & Liability" 'boolean '(#t #f))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show Net Worth" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "4a6b82e8678c4f3d9e85d9f09634ca89")
+         (cons 'report-name "Investment Portfolio")
+         (cons 'options
+               (list
+                (vector "Accounts" "Include accounts with no shares" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "44f81bee049b4b3ea908f8dac9a9474e")
+         (cons 'report-name "Income Over Time")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Chart Type" 'multichoice '(barchart linechart))
+                (vector "Display" "Use Stacked Charts" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show long account names" 'boolean '(#t #f))
+                (vector "General" "Show Average" 'multichoice '(None MonthDelta WeekDelta DayDelta))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "5bf27f249a0d11e7abc4cec278b6b50a")
+         (cons 'report-name "Income and GST Statement")
+         (cons 'options
+               (list
+                (vector "Display" "Description" 'boolean '(#t #f))
+                (vector "Display" "Num" 'boolean '(#t #f))
+                (vector "Display" "Tax payable" 'boolean '(#t #f))
+                (vector "Display" "Individual income columns" 'boolean '(#t #f))
+                (vector "Display" "Use Full Account Name" 'boolean '(#t #f))
+                (vector "Display" "Net Income" 'boolean '(#t #f))
+                (vector "Display" "Use Full Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Individual expense columns" 'boolean '(#t #f))
+                (vector "Display" "Account Code" 'boolean '(#t #f))
+                (vector "Display" "Individual tax columns" 'boolean '(#t #f))
+                (vector "Display" "Remittance amount" 'boolean '(#t #f))
+                (vector "Display" "Memo" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Account Name" 'boolean '(#t #f))
+                (vector "Display" "Other Account Name" 'boolean '(#t #f))
+                (vector "Display" "Other Account Code" 'boolean '(#t #f))
+                (vector "Display" "Reconciled Date" 'boolean '(#t #f))
+                (vector "Display" "Subtotal Table" 'boolean '(#t #f))
+                (vector "Display" "Notes" 'boolean '(#t #f))
+                (vector "Display" "Date" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show Full Account Name" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Code" 'boolean '(#t #f))
+                (vector "Sorting" "Show Account Description" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Secondary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Add indenting columns" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Primary Subtotal" 'boolean '(#t #f))
+                (vector "Sorting" "Secondary Subtotal for Date Key" 'multichoice '(none daily weekly monthly quarterly yearly))
+                (vector "Sorting" "Primary Key" 'multichoice '(account-name account-code date reconciled-date reconciled-status register-order corresponding-acc-name corresponding-acc-code amount description number t-number memo notes none))
+                (vector "Sorting" "Primary Sort Order" 'multichoice '(ascend descend))
+                (vector "Sorting" "Show subtotals only (hide transactional data)" 'boolean '(#t #f))
+                (vector "Filter" "Void Transactions" 'multichoice '(non-void-only void-only both))
+                (vector "Filter" "Use regular expressions for transaction filter" 'boolean '(#t #f))
+                (vector "Filter" "Use regular expressions for account name filter" 'boolean '(#t #f))
+                (vector "Filter" "Reconcile Status" 'multichoice '(all unreconciled cleared reconciled))
+                (vector "General" "Table for Exporting" 'boolean '(#t #f))
+                (vector "General" "Add options summary" 'multichoice '(no-match always never))
+                (vector "General" "Common Currency" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "3fe6dce77da24c66bdc8f8efdea7f9ac")
+         (cons 'report-name "Liabilities")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "Display" "Show long names" 'boolean '(#t #f))
+                (vector "Display" "Show Percents" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "5123a759ceb9483abf2182d01c140e8d")
+         (cons 'report-name "Printable Invoice")
+         (cons 'options
+               (list
+                (vector "Display" "Individual Taxes" 'boolean '(#t #f))
+                (vector "Display" "Billing Terms" 'boolean '(#t #f))
+                (vector "Display" "References" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Invoice Notes" 'boolean '(#t #f))
+                (vector "Display" "Billing ID" 'boolean '(#t #f))
+                (vector "Display" "Payments" 'boolean '(#t #f))
+                (vector "Display" "Job Details" 'boolean '(#t #f))
+                (vector "Display Columns" "Tax Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Discount" 'boolean '(#t #f))
+                (vector "Display Columns" "Total" 'boolean '(#t #f))
+                (vector "Display Columns" "Action" 'boolean '(#t #f))
+                (vector "Display Columns" "Taxable" 'boolean '(#t #f))
+                (vector "Display Columns" "Quantity" 'boolean '(#t #f))
+                (vector "Display Columns" "Price" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "21d7cfc59fc74f22887596ebde7e462d")
+         (cons 'report-name "Advanced Portfolio")
+         (cons 'options
+               (list
+                (vector "Accounts" "Include accounts with no shares" 'boolean '(#t #f))
+                (vector "Display" "Show ticker symbols" 'boolean '(#t #f))
+                (vector "Display" "Show number of shares" 'boolean '(#t #f))
+                (vector "Display" "Show prices" 'boolean '(#t #f))
+                (vector "Display" "Show listings" 'boolean '(#t #f))
+                (vector "General" "How to report brokerage fees" 'multichoice '(include-in-basis include-in-gain ignore-brokerage))
+                (vector "General" "Price Source" 'multichoice '(pricedb-latest pricedb-nearest))
+                (vector "General" "Basis calculation method" 'multichoice '(average-basis fifo-basis filo-basis))
+                (vector "General" "Set preference for price list data" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "7eb3df21073d4c33920a0257da15fba5")
+         (cons 'report-name "Receipt")
+         (cons 'options
+               (list
+                )))
+   (list (cons 'report-id "e6e34fa3b6e748debde3cb3bc76d3e53")
+         (cons 'report-name "Budget Flow")
+         (cons 'options
+               (list
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "b1f15b2052c149df93e698fe85a81ea6")
+         (cons 'report-name "Expense Over Time")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Chart Type" 'multichoice '(barchart linechart))
+                (vector "Display" "Use Stacked Charts" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show long account names" 'boolean '(#t #f))
+                (vector "General" "Show Average" 'multichoice '(None MonthDelta WeekDelta DayDelta))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "898d78ec92854402bf76e20a36d24ade")
+         (cons 'report-name "Hello, World")
+         (cons 'options
+               (list
+                (vector "Hello, World!" "Multi Choice Option" 'multichoice '(first second third fourth))
+                (vector "Hello, World!" "Boolean Option" 'boolean '(#t #f))
+                (vector "Testing" "Crash the report" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "5e2d129f28d14df881c3e47e3053f604")
+         (cons 'report-name "Income vs. Day of Week")
+         (cons 'options
+               (list
+                (vector "Accounts" "Include Sub-Accounts" 'boolean '(#t #f))
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "faf410e8f8da481fbc09e4763da40bcc")
+         (cons 'report-name "Liabilities Over Time")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Chart Type" 'multichoice '(barchart linechart))
+                (vector "Display" "Use Stacked Charts" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show long account names" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "0b81a3bdfd504aff849ec2e8630524bc")
+         (cons 'report-name "Income Statement")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries pattern is case-sensitive" 'boolean '(#t #f))
+                (vector "Entries" "Closing Entries Pattern is regular expression" 'boolean '(#t #f))
+                (vector "Display" "Display as a two column report" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Display in standard, income first, order" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Label the revenue section" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Include expense total" 'boolean '(#t #f))
+                (vector "Display" "Include trading accounts total" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Include revenue total" 'boolean '(#t #f))
+                (vector "Display" "Label the expense section" 'boolean '(#t #f))
+                (vector "Display" "Label the trading accounts section" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "415cd38d39054d9e9c4040455290c2b1")
+         (cons 'report-name "Budget Chart")
+         (cons 'options
+               (list
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Chart Type" 'multichoice '(bars lines))
+                (vector "Display" "Running Sum" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "f8748b813fab4220ba26e743aedf38da")
+         (cons 'report-name "Cash Flow")
+         (cons 'options
+               (list
+                (vector "Accounts" "Always show sub-accounts" 'boolean '(#t #f))
+                (vector "Accounts" "Include Trading Accounts in report" 'boolean '(#t #f))
+                (vector "Accounts" "Account Display Depth" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "General" "Show Full Account Names" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "65135608f2014c6ca8412793a8cdf169")
+         (cons 'report-name "Welcome to GnuCash")
+         (cons 'options
+               (list
+                )))
+   (list (cons 'report-id "d7d1e53505ee4b1b82efad9eacedaea0")
+         (cons 'report-name "Vendor Report")
+         (cons 'options
+               (list
+                (vector "Display Columns" "Tax" 'boolean '(#t #f))
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Debits" 'boolean '(#t #f))
+                (vector "Display Columns" "Type" 'boolean '(#t #f))
+                (vector "Display Columns" "Due Date" 'boolean '(#t #f))
+                (vector "Display Columns" "Sale" 'boolean '(#t #f))
+                (vector "Display Columns" "Reference" 'boolean '(#t #f))
+                (vector "Display Columns" "Credits" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                (vector "General" "Due or Post Date" 'multichoice '(duedate postdate))
+                (vector "__reg" "reverse?" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "5518ac227e474f47a34439f2d4d049de")
+         (cons 'report-name "Job Report")
+         (cons 'options
+               (list
+                (vector "Display Columns" "Description" 'boolean '(#t #f))
+                (vector "Display Columns" "Amount" 'boolean '(#t #f))
+                (vector "Display Columns" "Type" 'boolean '(#t #f))
+                (vector "Display Columns" "Due Date" 'boolean '(#t #f))
+                (vector "Display Columns" "Reference" 'boolean '(#t #f))
+                (vector "Display Columns" "Date" 'boolean '(#t #f))
+                (vector "__reg" "reverse?" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "e5fa5ce805e840ecbeca4dba3fa4ead9")
+         (cons 'report-name "Budget Profit & Loss")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Display" "Display as a two column report" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Display in standard, income first, order" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Label the revenue section" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Include expense total" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Include revenue total" 'boolean '(#t #f))
+                (vector "Display" "Label the expense section" 'boolean '(#t #f))
+                (vector "General" "Report for range of budget periods" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "e533c998186b11e1b2e2001558291366")
+         (cons 'report-name "Income & Expense Linechart")
+         (cons 'options
+               (list
+                (vector "Display" "Grid" 'boolean '(#t #f))
+                (vector "Display" "Data markers?" 'boolean '(#t #f))
+                (vector "Display" "Show Income/Expense" 'boolean '(#t #f))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show Net Profit" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "22104e02654c4adba844ee75a3f8d173")
+         (cons 'report-name "Register")
+         (cons 'options
+               (list
+                (vector "Display" "Description" 'boolean '(#t #f))
+                (vector "Display" "Account" 'boolean '(#t #f))
+                (vector "Display" "Amount" 'multichoice '(single double))
+                (vector "Display" "Num" 'boolean '(#t #f))
+                (vector "Display" "Running Balance" 'boolean '(#t #f))
+                (vector "Display" "Shares" 'boolean '(#t #f))
+                (vector "Display" "Lot" 'boolean '(#t #f))
+                (vector "Display" "Memo" 'boolean '(#t #f))
+                (vector "Display" "Totals" 'boolean '(#t #f))
+                (vector "Display" "Price" 'boolean '(#t #f))
+                (vector "Display" "Date" 'boolean '(#t #f))
+                (vector "Display" "Value" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "d8b63264186b11e19038001558291366")
+         (cons 'report-name "Net Worth Linechart")
+         (cons 'options
+               (list
+                (vector "Display" "Grid" 'boolean '(#t #f))
+                (vector "Display" "Show Asset & Liability" 'boolean '(#t #f))
+                (vector "Display" "Data markers?" 'boolean '(#t #f))
+                (vector "Display" "Show table" 'boolean '(#t #f))
+                (vector "Display" "Show Net Worth" 'boolean '(#t #f))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "1d241609fd4644caad765c95be20ff4c")
+         (cons 'report-name "Price")
+         (cons 'options
+               (list
+                (vector "Display" "Marker" 'multichoice '(diamond circle square cross plus dash filleddiamond filledcircle filledsquare))
+                (vector "Price" "Invert prices" 'boolean '(#t #f))
+                (vector "Price" "Price Source" 'multichoice '(weighted-average actual-transactions pricedb))
+                (vector "General" "Step Size" 'multichoice '(DayDelta WeekDelta TwoWeekDelta MonthDelta QuarterDelta HalfYearDelta YearDelta))
+                )))
+   (list (cons 'report-id "ecc35ea9dbfa4e20ba389fc85d59cb69")
+         (cons 'report-name "Budget Balance Sheet")
+         (cons 'options
+               (list
+                (vector "Commodities" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                (vector "Commodities" "Show Exchange Rates" 'boolean '(#t #f))
+                (vector "Commodities" "Show Foreign Currencies" 'boolean '(#t #f))
+                (vector "Accounts" "Levels of Subaccounts" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Accounts" "Flatten list to depth limit" 'boolean '(#t #f))
+                (vector "Display" "Show accounting-style rules" 'boolean '(#t #f))
+                (vector "Display" "Display accounts as hyperlinks" 'boolean '(#t #f))
+                (vector "Display" "Parent account balances" 'multichoice '(immediate-bal recursive-bal omit-bal))
+                (vector "Display" "Include assets total" 'boolean '(#t #f))
+                (vector "Display" "Include accounts with zero total balances" 'boolean '(#t #f))
+                (vector "Display" "Parent account subtotals" 'multichoice '(t f canonically-tabbed))
+                (vector "Display" "Include liabilities total" 'boolean '(#t #f))
+                (vector "Display" "Label the liabilities section" 'boolean '(#t #f))
+                (vector "Display" "Include new/existing totals" 'boolean '(#t #f))
+                (vector "Display" "Omit zero balance figures" 'boolean '(#t #f))
+                (vector "Display" "Label the assets section" 'boolean '(#t #f))
+                (vector "Display" "Include equity total" 'boolean '(#t #f))
+                (vector "Display" "Label the equity section" 'boolean '(#t #f))
+                (vector "General" "Single column Balance Sheet" 'boolean '(#t #f))
+                )))
+   (list (cons 'report-id "5c7fd8a1fe9a4cd38884ff54214aa88a")
+         (cons 'report-name "Assets")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "Display" "Show long names" 'boolean '(#t #f))
+                (vector "Display" "Show Percents" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   (list (cons 'report-id "9bf1892805cb4336be6320fe48ce5446")
+         (cons 'report-name "Expense Accounts")
+         (cons 'options
+               (list
+                (vector "Accounts" "Show Accounts until level" 'multichoice '(all 1 2 3 4 5 6))
+                (vector "Display" "Show Totals" 'boolean '(#t #f))
+                (vector "Display" "Show long names" 'boolean '(#t #f))
+                (vector "Display" "Show Percents" 'boolean '(#t #f))
+                (vector "Display" "Sort Method" 'multichoice '(acct-code alphabetical amount))
+                (vector "General" "Show Average" 'multichoice '(None YearDelta MonthDelta WeekDelta))
+                (vector "General" "Price Source" 'multichoice '(average-cost weighted-average pricedb-latest pricedb-nearest))
+                )))
+   ))

commit 694d0f06133826ecc036b026fbf0290455206b13
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 10 23:31:36 2018 +0800

    [budget-flow] fix report-title not defined
    
    this report uses reportname instead

diff --git a/gnucash/report/standard-reports/budget-flow.scm b/gnucash/report/standard-reports/budget-flow.scm
index f718968..d15b81e 100644
--- a/gnucash/report/standard-reports/budget-flow.scm
+++ b/gnucash/report/standard-reports/budget-flow.scm
@@ -295,7 +295,7 @@
         (gnc:html-document-add-object!
           doc
             (gnc:html-make-no-account-warning 
-              report-title (gnc:report-id report-obj))))
+              reportname (gnc:report-id report-obj))))
 
       ((not budget-valid?)
         ;; No budget selected.

commit 57c6f175b442988c2135dc250c39b4c7d8e726be
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 10 23:30:55 2018 +0800

    [html-chart] num-columns return 0 for empty-table
    
    this commit fixes whereby data is '() indicating no columns

diff --git a/gnucash/report/report-system/html-table.scm b/gnucash/report/report-system/html-table.scm
index 41a615f..97615b6 100644
--- a/gnucash/report/report-system/html-table.scm
+++ b/gnucash/report/report-system/html-table.scm
@@ -326,7 +326,7 @@
   (record-modifier <html-table> 'num-rows))
 
 (define (gnc:html-table-num-columns table)
-  (apply max (map length (gnc:html-table-data table))))
+  (apply max (cons 0 (map length (gnc:html-table-data table)))))
 
 (define (gnc:html-table-append-row/markup! table markup newrow)
   (let ((rownum (gnc:html-table-append-row! table newrow)))

commit e2907844be26d61914385e3932fe1de2ca31b588
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Jul 10 23:30:26 2018 +0800

    [customer-summary] prevents crash on empty-book with no accounts

diff --git a/gnucash/report/business-reports/customer-summary.scm b/gnucash/report/business-reports/customer-summary.scm
index b0384be..f6e05c5 100644
--- a/gnucash/report/business-reports/customer-summary.scm
+++ b/gnucash/report/business-reports/customer-summary.scm
@@ -699,7 +699,7 @@
          (expense-accounts (opt-val pagename-expenseaccounts optname-expenseaccounts))
          (income-accounts (opt-val pagename-incomeaccounts optname-incomeaccounts))
          (all-accounts (append income-accounts expense-accounts))
-         (book (gnc-account-get-book (car all-accounts)))
+         (book (gnc-get-current-book))
          (date-format (gnc:options-fancy-date book))
          (type (opt-val "__reg" "owner-type"))
          (reverse? (opt-val "__reg" "reverse?"))



Summary of changes:
 borrowed/jenny/jenny.c                             | 1806 ++++++++++++++++++++
 gnucash/gschemas/org.gnucash.gschema.xml.in        |    2 +-
 gnucash/gtkbuilder/dialog-preferences.glade        |    2 +-
 .../report/business-reports/customer-summary.scm   |    2 +-
 gnucash/report/report-system/html-table.scm        |    2 +-
 gnucash/report/standard-reports/budget-flow.scm    |    2 +-
 .../report/standard-reports/test/CMakeLists.txt    |    1 +
 .../standard-reports/test/test-stress-options.scm  |  339 ++++
 .../test/test-stress-optionslist.scm               |  972 +++++++++++
 9 files changed, 3123 insertions(+), 5 deletions(-)
 create mode 100644 borrowed/jenny/jenny.c
 create mode 100644 gnucash/report/standard-reports/test/test-stress-options.scm
 create mode 100644 gnucash/report/standard-reports/test/test-stress-optionslist.scm



More information about the gnucash-changes mailing list