/*
|
|
* CS:APP Data Lab
|
|
*
|
|
* btest.c - A test harness that checks a student's solution in bits.c
|
|
* for correctness.
|
|
*
|
|
* Copyright (c) 2001-2011, R. Bryant and D. O'Hallaron, All rights
|
|
* reserved. May not be used, modified, or copied without permission.
|
|
*
|
|
* This is an improved version of btest that tests large windows
|
|
* around zero and tmin and tmax for integer puzzles, and zero, norm,
|
|
* and denorm boundaries for floating point puzzles.
|
|
*
|
|
* Note: not 64-bit safe. Always compile with gcc -m32 option.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
#include <math.h>
|
|
#include "btest.h"
|
|
|
|
/* Not declared in some stdlib.h files, so define here */
|
|
float strtof(const char *nptr, char **endptr);
|
|
|
|
/*************************
|
|
* Configuration Constants
|
|
*************************/
|
|
|
|
/* Handle infinite loops by setting upper limit on execution time, in
|
|
seconds */
|
|
#define TIMEOUT_LIMIT 10
|
|
|
|
/* For functions with a single argument, generate TEST_RANGE values
|
|
above and below the min and max test values, and above and below
|
|
zero. Functions with two or three args will use square and cube
|
|
roots of this value, respectively, to avoid combinatorial
|
|
explosion */
|
|
#define TEST_RANGE 500000
|
|
|
|
/* This defines the maximum size of any test value array. The
|
|
gen_vals() routine creates k test values for each value of
|
|
TEST_RANGE, thus MAX_TEST_VALS must be at least k*TEST_RANGE */
|
|
#define MAX_TEST_VALS 13*TEST_RANGE
|
|
|
|
/**********************************
|
|
* Globals defined in other modules
|
|
**********************************/
|
|
/* This characterizes the set of puzzles to test.
|
|
Defined in decl.c and generated from templates in ./puzzles dir */
|
|
extern test_rec test_set[];
|
|
|
|
/************************************************
|
|
* Write-once globals defined by command line args
|
|
************************************************/
|
|
|
|
/* Emit results in a format for autograding, without showing
|
|
and counter-examples */
|
|
static int grade = 0;
|
|
|
|
/* Time out after this number of seconds */
|
|
static int timeout_limit = TIMEOUT_LIMIT; /* -T */
|
|
|
|
/* If non-NULL, test only one function (-f) */
|
|
static char* test_fname = NULL;
|
|
|
|
/* Special case when only use fixed argument(s) (-1, -2, or -3) */
|
|
static int has_arg[3] = {0,0,0};
|
|
static unsigned argval[3] = {0,0,0};
|
|
|
|
/* Use fixed weight for rating, and if so, what should it be? (-r) */
|
|
static int global_rating = 0;
|
|
|
|
/******************
|
|
* Helper functions
|
|
******************/
|
|
|
|
/*
|
|
* Signal - installs a signal handler
|
|
*/
|
|
typedef void handler_t(int);
|
|
|
|
handler_t *Signal(int signum, handler_t *handler)
|
|
{
|
|
struct sigaction action, old_action;
|
|
|
|
action.sa_handler = handler;
|
|
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
|
|
action.sa_flags = SA_RESTART; /* restart syscalls if possible */
|
|
|
|
if (sigaction(signum, &action, &old_action) < 0)
|
|
perror("Signal error");
|
|
return (old_action.sa_handler);
|
|
}
|
|
|
|
/*
|
|
* timeout_handler - SIGALARM hander
|
|
*/
|
|
sigjmp_buf envbuf;
|
|
void timeout_handler(int sig) {
|
|
siglongjmp(envbuf, 1);
|
|
}
|
|
|
|
/*
|
|
* random_val - Return random integer value between min and max
|
|
*/
|
|
static int random_val(int min, int max)
|
|
{
|
|
double weight = rand()/(double) RAND_MAX;
|
|
int result = min * (1-weight) + max * weight;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* gen_vals - Generate the integer values we'll use to test a function
|
|
*/
|
|
static int gen_vals(int test_vals[], int min, int max, int test_range, int arg)
|
|
{
|
|
int i;
|
|
int test_count = 0;
|
|
|
|
/* Special case: If the user has specified a specific function
|
|
argument using the -1, -2, or -3 flags, then simply use this
|
|
argument and return */
|
|
if (has_arg[arg]) {
|
|
test_vals[0] = argval[arg];
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Special case: Generate test vals for floating point functions
|
|
* where the input argument is an unsigned bit-level
|
|
* representation of a float. For this case we want to test the
|
|
* regions around zero, the smallest normalized and largest
|
|
* denormalized numbers, one, and the largest normalized number,
|
|
* as well as inf and nan.
|
|
*/
|
|
if ((min == 1 && max == 1)) {
|
|
unsigned smallest_norm = 0x00800000;
|
|
unsigned one = 0x3f800000;
|
|
unsigned largest_norm = 0x7f000000;
|
|
|
|
unsigned inf = 0x7f800000;
|
|
unsigned nan = 0x7fc00000;
|
|
unsigned sign = 0x80000000;
|
|
|
|
/* Test range should be at most 1/2 the range of one exponent
|
|
value */
|
|
if (test_range > (1 << 23)) {
|
|
test_range = 1 << 23;
|
|
}
|
|
|
|
/* Functions where the input argument is an unsigned bit-level
|
|
representation of a float. The number of tests generated
|
|
inside this loop body is the value k referenced in the
|
|
comment for the global variable MAX_TEST_VALS. */
|
|
|
|
for (i = 0; i < test_range; i++) {
|
|
/* Denorms around zero */
|
|
test_vals[test_count++] = i;
|
|
test_vals[test_count++] = sign | i;
|
|
|
|
/* Region around norm to denorm transition */
|
|
test_vals[test_count++] = smallest_norm + i;
|
|
test_vals[test_count++] = smallest_norm - i;
|
|
test_vals[test_count++] = sign | (smallest_norm + i);
|
|
test_vals[test_count++] = sign | (smallest_norm - i);
|
|
|
|
/* Region around one */
|
|
test_vals[test_count++] = one + i;
|
|
test_vals[test_count++] = one - i;
|
|
test_vals[test_count++] = sign | (one + i);
|
|
test_vals[test_count++] = sign | (one - i);
|
|
|
|
/* Region below largest norm */
|
|
test_vals[test_count++] = largest_norm - i;
|
|
test_vals[test_count++] = sign | (largest_norm - i);
|
|
}
|
|
|
|
/* special vals */
|
|
test_vals[test_count++] = inf; /* inf */
|
|
test_vals[test_count++] = sign | inf; /* -inf */
|
|
test_vals[test_count++] = nan; /* nan */
|
|
test_vals[test_count++] = sign | nan; /* -nan */
|
|
|
|
return test_count;
|
|
}
|
|
|
|
|
|
/*
|
|
* Normal case: Generate test vals for integer functions
|
|
*/
|
|
|
|
/* If the range is small enough, then do exhaustively */
|
|
if (max - MAX_TEST_VALS <= min) {
|
|
for (i = min; i <= max; i++)
|
|
test_vals[test_count++] = i;
|
|
return test_count;
|
|
}
|
|
|
|
/* Otherwise, need to sample. Do so near the boundaries, around
|
|
zero, and for some random cases. */
|
|
for (i = 0; i < test_range; i++) {
|
|
|
|
/* Test around the boundaries */
|
|
test_vals[test_count++] = min + i;
|
|
test_vals[test_count++] = max - i;
|
|
|
|
/* If zero falls between min and max, then also test around zero */
|
|
if (i >= min && i <= max)
|
|
test_vals[test_count++] = i;
|
|
if (-i >= min && -i <= max)
|
|
test_vals[test_count++] = -i;
|
|
|
|
/* Random case between min and max */
|
|
test_vals[test_count++] = random_val(min, max);
|
|
|
|
}
|
|
return test_count;
|
|
}
|
|
|
|
/*
|
|
* test_0_arg - Test a function with zero arguments
|
|
*/
|
|
static int test_0_arg(funct_t f, funct_t ft, char *name)
|
|
{
|
|
int r = f();
|
|
int rt = ft();
|
|
int error = (r != rt);
|
|
|
|
if (error && !grade)
|
|
printf("ERROR: Test %s() failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, r, r, rt, rt);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* test_1_arg - Test a function with one argument
|
|
*/
|
|
static int test_1_arg(funct_t f, funct_t ft, int arg1, char *name)
|
|
{
|
|
funct1_t f1 = (funct1_t) f;
|
|
funct1_t f1t = (funct1_t) ft;
|
|
int r, rt, error;
|
|
|
|
r = f1(arg1);
|
|
rt = f1t(arg1);
|
|
error = (r != rt);
|
|
if (error && !grade)
|
|
printf("ERROR: Test %s(%d[0x%x]) failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, arg1, arg1, r, r, rt, rt);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* test_2_arg - Test a function with two arguments
|
|
*/
|
|
static int test_2_arg(funct_t f, funct_t ft, int arg1, int arg2, char *name)
|
|
{
|
|
funct2_t f2 = (funct2_t) f;
|
|
funct2_t f2t = (funct2_t) ft;
|
|
int r = f2(arg1, arg2);
|
|
int rt = f2t(arg1, arg2);
|
|
int error = (r != rt);
|
|
|
|
if (error && !grade)
|
|
printf("ERROR: Test %s(%d[0x%x],%d[0x%x]) failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, arg1, arg1, arg2, arg2, r, r, rt, rt);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* test_3_arg - Test a function with three arguments
|
|
*/
|
|
static int test_3_arg(funct_t f, funct_t ft,
|
|
int arg1, int arg2, int arg3, char *name)
|
|
{
|
|
funct3_t f3 = (funct3_t) f;
|
|
funct3_t f3t = (funct3_t) ft;
|
|
int r = f3(arg1, arg2, arg3);
|
|
int rt = f3t(arg1, arg2, arg3);
|
|
int error = (r != rt);
|
|
|
|
if (error && !grade)
|
|
printf("ERROR: Test %s(%d[0x%x],%d[0x%x],%d[0x%x]) failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, arg1, arg1, arg2, arg2, arg3, arg3, r, r, rt, rt);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* test_function - Test a function. Return number of errors
|
|
*/
|
|
static int test_function(test_ptr t) {
|
|
int test_counts[3]; /* number of test values for each arg */
|
|
int args = t->args; /* number of function arguments */
|
|
int arg_test_range[3]; /* test range for each argument */
|
|
int i, a1, a2, a3;
|
|
int errors = 0;
|
|
|
|
/* These are the test values for each arg. Declared with the
|
|
static attribute so that the array will be allocated in bss
|
|
rather than the stack */
|
|
static int arg_test_vals[3][MAX_TEST_VALS];
|
|
|
|
/* Sanity check on the number of args */
|
|
if (args < 0 || args > 3) {
|
|
printf("Configuration error: invalid number of args (%d) for function %s\n", args, t->name);
|
|
exit(1);
|
|
}
|
|
|
|
/* Assign range of argument test vals so as to conserve the total
|
|
number of tests, independent of the number of arguments */
|
|
if (args == 1) {
|
|
arg_test_range[0] = TEST_RANGE;
|
|
}
|
|
else if (args == 2) {
|
|
arg_test_range[0] = pow((double)TEST_RANGE, 0.5); /* sqrt */
|
|
arg_test_range[1] = arg_test_range[0];
|
|
}
|
|
else {
|
|
arg_test_range[0] = pow((double)TEST_RANGE, 0.333); /* cbrt */
|
|
arg_test_range[1] = arg_test_range[0];
|
|
arg_test_range[2] = arg_test_range[0];
|
|
}
|
|
|
|
/* Sanity check on the ranges */
|
|
if (arg_test_range[0] < 1)
|
|
arg_test_range[0] = 1;
|
|
if (arg_test_range[1] < 1)
|
|
arg_test_range[1] = 1;
|
|
if (arg_test_range[2] < 1)
|
|
arg_test_range[2] = 1;
|
|
|
|
/* Create a test set for each argument */
|
|
for (i = 0; i < args; i++) {
|
|
test_counts[i] = gen_vals(arg_test_vals[i],
|
|
t->arg_ranges[i][0], /* min */
|
|
t->arg_ranges[i][1], /* max */
|
|
arg_test_range[i],
|
|
i);
|
|
|
|
}
|
|
|
|
/* Handle timeouts in the test code */
|
|
if (timeout_limit > 0) {
|
|
int rc;
|
|
rc = sigsetjmp(envbuf, 1);
|
|
if (rc) {
|
|
/* control will reach here if there is a timeout */
|
|
errors = 1;
|
|
printf("ERROR: Test %s failed.\n Timed out after %d secs (probably infinite loop)\n", t->name, timeout_limit);
|
|
return errors;
|
|
}
|
|
alarm(timeout_limit);
|
|
}
|
|
|
|
|
|
/* Test function has no arguments */
|
|
if (args == 0) {
|
|
errors += test_0_arg(t->solution_funct, t->test_funct, t->name);
|
|
return errors;
|
|
}
|
|
|
|
/*
|
|
* Test function has at least one argument
|
|
*/
|
|
|
|
/* Iterate over the values for first argument */
|
|
|
|
for (a1 = 0; a1 < test_counts[0]; a1++) {
|
|
if (args == 1) {
|
|
errors += test_1_arg(t->solution_funct,
|
|
t->test_funct,
|
|
arg_test_vals[0][a1],
|
|
t->name);
|
|
|
|
/* Stop testing if there is an error */
|
|
if (errors)
|
|
return errors;
|
|
}
|
|
else {
|
|
/* if necessary, iterate over values for second argument */
|
|
for (a2 = 0; a2 < test_counts[1]; a2++) {
|
|
if (args == 2) {
|
|
errors += test_2_arg(t->solution_funct,
|
|
t->test_funct,
|
|
arg_test_vals[0][a1],
|
|
arg_test_vals[1][a2],
|
|
t->name);
|
|
|
|
/* Stop testing if there is an error */
|
|
if (errors)
|
|
return errors;
|
|
}
|
|
else {
|
|
/* if necessary, iterate over vals for third arg */
|
|
for (a3 = 0; a3 < test_counts[2]; a3++) {
|
|
errors += test_3_arg(t->solution_funct,
|
|
t->test_funct,
|
|
arg_test_vals[0][a1],
|
|
arg_test_vals[1][a2],
|
|
arg_test_vals[2][a3],
|
|
t->name);
|
|
|
|
/* Stop testing if there is an error */
|
|
if (errors)
|
|
return errors;
|
|
} /* a3 */
|
|
}
|
|
} /* a2 */
|
|
}
|
|
} /* a1 */
|
|
|
|
|
|
return errors;
|
|
}
|
|
|
|
/*
|
|
* run_tests - Run series of tests. Return number of errors
|
|
*/
|
|
static int run_tests()
|
|
{
|
|
int i;
|
|
int errors = 0;
|
|
double points = 0.0;
|
|
double max_points = 0.0;
|
|
|
|
printf("Score\tRating\tErrors\tFunction\n");
|
|
|
|
for (i = 0; test_set[i].solution_funct; i++) {
|
|
int terrors;
|
|
double tscore;
|
|
double tpoints;
|
|
if (!test_fname || strcmp(test_set[i].name,test_fname) == 0) {
|
|
int rating = global_rating ? global_rating : test_set[i].rating;
|
|
terrors = test_function(&test_set[i]);
|
|
errors += terrors;
|
|
tscore = terrors == 0 ? 1.0 : 0.0;
|
|
tpoints = rating * tscore;
|
|
points += tpoints;
|
|
max_points += rating;
|
|
|
|
if (grade || terrors < 1)
|
|
printf(" %.0f\t%d\t%d\t%s\n",
|
|
tpoints, rating, terrors, test_set[i].name);
|
|
|
|
}
|
|
}
|
|
|
|
printf("Total points: %.0f/%.0f\n", points, max_points);
|
|
return errors;
|
|
}
|
|
|
|
/*
|
|
* get_num_val - Extract hex/decimal/or float value from string
|
|
*/
|
|
static int get_num_val(char *sval, unsigned *valp) {
|
|
char *endp;
|
|
|
|
/* See if it's an integer or floating point */
|
|
int ishex = 0;
|
|
int isfloat = 0;
|
|
int i;
|
|
for (i = 0; sval[i]; i++) {
|
|
switch (sval[i]) {
|
|
case 'x':
|
|
case 'X':
|
|
ishex = 1;
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
if (!ishex)
|
|
isfloat = 1;
|
|
break;
|
|
case '.':
|
|
isfloat = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (isfloat) {
|
|
float fval = strtof(sval, &endp);
|
|
if (!*endp) {
|
|
*valp = *(unsigned *) &fval;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
} else {
|
|
long long int llval = strtoll(sval, &endp, 0);
|
|
long long int upperbits = llval >> 31;
|
|
/* will give -1 for negative, 0 or 1 for positive */
|
|
if (!*valp && (upperbits == 0 || upperbits == -1 || upperbits == 1)) {
|
|
*valp = (unsigned) llval;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* usage - Display usage info
|
|
*/
|
|
static void usage(char *cmd) {
|
|
printf("Usage: %s [-hg] [-r <n>] [-f <name> [-1|-2|-3 <val>]*] [-T <time limit>]\n", cmd);
|
|
printf(" -1 <val> Specify first function argument\n");
|
|
printf(" -2 <val> Specify second function argument\n");
|
|
printf(" -3 <val> Specify third function argument\n");
|
|
printf(" -f <name> Test only the named function\n");
|
|
printf(" -g Compact output for grading (with no error msgs)\n");
|
|
printf(" -h Print this message\n");
|
|
printf(" -r <n> Give uniform weight of n for all problems\n");
|
|
printf(" -T <lim> Set timeout limit to lim\n");
|
|
exit(1);
|
|
}
|
|
|
|
|
|
/**************
|
|
* Main routine
|
|
**************/
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char c;
|
|
|
|
/* parse command line args */
|
|
while ((c = getopt(argc, argv, "hgf:r:T:1:2:3:")) != -1)
|
|
switch (c) {
|
|
case 'h': /* help */
|
|
usage(argv[0]);
|
|
break;
|
|
case 'g': /* grading option for autograder */
|
|
grade = 1;
|
|
break;
|
|
case 'f': /* test only one function */
|
|
test_fname = strdup(optarg);
|
|
break;
|
|
case 'r': /* set global rating for each problem */
|
|
global_rating = atoi(optarg);
|
|
if (global_rating < 0)
|
|
usage(argv[0]);
|
|
break;
|
|
case '1': /* Get first argument */
|
|
has_arg[0] = get_num_val(optarg, &argval[0]);
|
|
if (!has_arg[0]) {
|
|
printf("Bad argument '%s'\n", optarg);
|
|
exit(0);
|
|
}
|
|
break;
|
|
case '2': /* Get first argument */
|
|
has_arg[1] = get_num_val(optarg, &argval[1]);
|
|
if (!has_arg[1]) {
|
|
printf("Bad argument '%s'\n", optarg);
|
|
exit(0);
|
|
}
|
|
break;
|
|
case '3': /* Get first argument */
|
|
has_arg[2] = get_num_val(optarg, &argval[2]);
|
|
if (!has_arg[2]) {
|
|
printf("Bad argument '%s'\n", optarg);
|
|
exit(0);
|
|
}
|
|
break;
|
|
case 'T': /* Set timeout limit */
|
|
timeout_limit = atoi(optarg);
|
|
break;
|
|
default:
|
|
usage(argv[0]);
|
|
}
|
|
|
|
if (timeout_limit > 0) {
|
|
Signal(SIGALRM, timeout_handler);
|
|
}
|
|
|
|
/* test each function */
|
|
run_tests();
|
|
|
|
return 0;
|
|
}
|