/* ------------------------------------------------------------------------------- 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 #include #include #include /* ------------------------------------------------------------------------------- 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>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; im[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 (ilen) { 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 (ilen && (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; dndim; ++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; dndim; ++d) { if (s->tu[d]) { for (f=0; fdim[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; dndim; ++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; dndim; ++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; indim; ++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; itu)->len; ++*ctx->count; return TRUE; } /* print out a single tuple */ void show_tuple( feature *fe, ub2 len) { ub4 i; for (i=0; ife; ub1 n = ctx->n; --(*ctx->tu)->len; --*ctx->count; fe = &(*ctx->tu)->fe[(*ctx->tu)->len * n]; for (i=0; itu)->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 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; indim; ++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; if = (ub2 *)my_alloc( s, sizeof(ub2)*s->ndim); if (!add_test(s, t)) { goto failure; } for (i=0; indim; ++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; indim; ++i) used[i] = FALSE; if (t != TOKEN_NUMBER) { printf("jenny: -w is ...\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[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; ife[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; dndim; ++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; dndim; ++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; fdim[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; ilen; ++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= '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= '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; in_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; if[i]]); } printf(" \n"); } /* print out all the tests */ void report_all( state *s) { ub4 i; for (i=0; intests; ++i) { report(s->t[i], s->ndim); } } void start_builder( state *s, feature *tuple, ub1 n) { ub1 i; for (i=0; indim-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; in[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; ituple_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; jntests; ++j) { test *t = s->t[j]; for (i=0; if[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; ituple_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; indim; ++i) { if (mut[i] && s->wc[i]) { s->dimord[ndim++] = i; } } /* hillclimbing, with sidestepping, minimize number of withouts hit */ for (i=0; i0; --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; kdim[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; indim; ++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; idimord[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; fdim[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; indim; ++i) mut[i] = 1; for (i=0; indim; ++i) { t->f[i] = flearand(&s->r) % (s->dim[i]); } /* Plug in the chosen new tuple */ for (i=0; if[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; dndim; ++d) { ub2 f; for (f=0; fdim[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 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; iw = 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; ife[i].d = extra[i].d; w->fe[i].f = extra[i].f; } for (d=0; dndim; ++d) { ub2 f; tu_iter ctx; for (f=0; fdim[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; dndim; ++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; tntests; ++t) { for (d=0; dndim; ++d) { s->onec[t][d] = 0; } } /* Iterate through all the tuples */ start_builder( s, tuple, n); for (;;) { sb4 i; ub2 thistest; for (i=0; ituple_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; intests; ++i) { ub1 j; for (j=0; jt[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; intests; ++i) { ub1 j; for (j=0; jt[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; jone[thistest][tuple[j].d], n, &s->onec[thistest][tuple[j].d]); (void)insert_tuple(s, &ctx, tuple); } } } make_next_tuple: for (i=0; ituple_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; tntests; ++t) { ub4 i, j=0; for (i=0; indim; ++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; ituple_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; jntests; ++j) { test *t = s->t[j]; for (i=0; if[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; ituple_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