/* * test-framecode.c -- test tclib framecode handling * Written by Andrew Church * * This file is part of transcode, a video stream processing tool. * transcode is free software, distributable under the terms of the GNU * General Public License (version 2 or later). See the file COPYING * for details. */ /* We define our own dummy free() for testing, so avoid string.h declaring * it over us */ #define free string_h_free #include #undef free #include #include #include #include #include #include #include "libtc/framecode.h" /* Global verbosity level (0: silent, 1: test list, 2: debug info) */ static int verbose = 1; /*************************************************************************/ /* Fake free() function, to enable testing of free_fc_time(). */ #define FREED_POINTER ((struct fc_time *)1) void free(void *fct); /* prototype, to avoid warnings */ void free(void *fct) { ((struct fc_time *)fct)->next = FREED_POINTER; } /*************************************************************************/ /* Helper routines for test_new_fc_time_from_string() to extract an integer * or a framecode from a string and advance the string pointer. Both * routines return nonzero on success, zero on failure. */ static int get_uint(char **sptr, unsigned int *valptr) { char *s = *sptr; unsigned long lval; if (*s < '0' || *s > '9') return 0; errno = 0; lval = strtoul(s, sptr, 10); if (errno == ERANGE #if ULONG_MAX > UINT_MAX || lval > UINT_MAX #endif ) { return 0; } *valptr = (unsigned int)lval; return 1; } static int get_fc(char **sptr, unsigned int *frameptr, double fps) { char *s = *sptr; unsigned int frame, temp; int is_time = 0; if (!get_uint(&s, &frame)) return 0; if (*s == ':') { is_time = 1; s++; if (!get_uint(&s, &temp)) return 0; frame = frame*60 + temp; if (*s == ':') { s++; if (!get_uint(&s, &temp)) return 0; frame = frame*60 + temp; } } if (*s == '.' || is_time) { frame = (unsigned int)floor(frame * fps); } if (*s == '.') { s++; if (!get_uint(&s, &temp)) return 0; frame += temp; } *sptr = s; *frameptr = frame; return 1; } /*************************************************************************/ /*************************************************************************/ /* Check that new_fc_time properly clears the fields of the allocated * fc_time structure. */ static int test_new_fc_time(void) { struct fc_time *fct; fct = new_fc_time(); return fct->next == NULL && fct->fps == 0.0 && fct->stepf == 0 && fct->vob_offset == 0 && fct->sh == 0 && fct->sm == 0 && fct->ss == 0 && fct->sf == 0 && fct->stf == 0 && fct->eh == 0 && fct->em == 0 && fct->es == 0 && fct->ef == 0 && fct->etf == 0; } /*************************************************************************/ /* Check that free_fc_time() properly frees all elements in a list. */ static int test_free_fc_time(void) { struct fc_time fct1, fct2, fct3; fct1.next = &fct2; fct2.next = &fct3; fct3.next = NULL; free_fc_time(&fct1); return fct1.next == FREED_POINTER && fct2.next == FREED_POINTER && fct3.next == FREED_POINTER; } /*************************************************************************/ /* Check that set_fc_time with the given parameters sets the fields of the * fc_time structure properly. */ static int test_set_fc_time(int start, int end, double fps) { struct fc_time fct; fct.next = NULL; fct.fps = fps; fct.stepf = 0; fct.vob_offset = 0; fct.sh = fct.sm = fct.ss = fct.sf = fct.stf = ~0; fct.eh = fct.em = fct.es = fct.ef = fct.etf = ~0; set_fc_time(&fct, start, end); if (verbose >= 2) { printf("[%d->%u:%u:%u.%u|%u - %d->%u:%u:%u.%u|%u @ %.1f] ", start, fct.sh, fct.sm, fct.ss, fct.sf, fct.stf, end, fct.eh, fct.em, fct.es, fct.ef, fct.etf, fps); } return fct.sh == (int)floor(start/fps) / 3600 && fct.sm == (int)floor(start/fps) / 60 % 60 && fct.ss == (int)floor(start/fps) % 60 && fct.sf == floor(start - ((int)floor(start/fps))*fps) && fct.stf == start && fct.eh == (int)floor(end/fps) / 3600 && fct.em == (int)floor(end/fps) / 60 % 60 && fct.es == (int)floor(end/fps) % 60 && fct.ef == floor(end - ((int)floor(end/fps))*fps) && fct.etf == end; } /*************************************************************************/ /* Check that fc_time_contains properly determines whether a given frame * number is contained in a list of up to three fc_time structures. When * testing with less than three fc_time structures, use -1 for the start * and end values as follows: * Two structures -> test(frame, fps, start1, end1, start2, end2, -1, -1) * One structure -> test(frame, fps, start1, end1, -1, -1, -1, -1) * Assumes that set_fc_time() works correctly. */ static int test_fc_time_contains(int frame, double fps, int start1, int end1, int start2, int end2, int start3, int end3) { struct fc_time fct1, fct2, fct3; int expected; /* Do we expect it to be found or not? */ int result; /* What we actually got out of the function */ fct1.next = NULL; fct1.fps = fps; fct1.stepf = 0; fct1.vob_offset = 0; fct2.next = NULL; fct2.fps = fps; fct2.stepf = 0; fct2.vob_offset = 0; fct3.next = NULL; fct3.fps = fps; fct3.stepf = 0; fct3.vob_offset = 0; set_fc_time(&fct1, start1, end1); expected = (frame >= start1 && frame < end1); if (start2 >= 0 && end2 >= 0) { fct1.next = &fct2; set_fc_time(&fct2, start2, end2); expected |= (frame >= start2 && frame < end2); if (start3 >= 0 && end3 >= 0) { fct2.next = &fct3; set_fc_time(&fct3, start3, end3); expected |= (frame >= start3 && frame < end3); } } result = fc_time_contains(&fct1, frame); return (expected && result) || (!expected && !result); } /*************************************************************************/ /* Check that new_fc_time_from_string() properly parses the given string. * Assumes that new_fc_time() and set_fc_time() work correctly. */ static int test_new_fc_time_from_string(const char *string, const char *separator, double fps) { struct fc_time *fctret, *fctexpect, *tail, *fct; char strsave[1000], *s; if (strlen(string) > sizeof(strsave)-1) { fprintf(stderr, "*** test_new_fc_time_from_string(): string too long" " (max %u chars)\n", (unsigned int)sizeof(strsave)-1); return 0; } /* Call the function itself */ fctret = new_fc_time_from_string(string, separator, fps, verbose>=2 ? 1 : -1); /* Figure out what we're supposed to get; if we're supposed to get an * error, return success or failure at that point */ fctexpect = tail = NULL; snprintf(strsave, sizeof(strsave), "%s", string); for (s = strtok(strsave,separator); s; s = strtok(NULL,separator)) { unsigned int start = 0, end = 0, stepf = 1; if (!get_fc(&s, &start, fps) || *s++ != '-' || !get_fc(&s, &end, fps) ) { return fctret == NULL; } if (*s == '/') { s++; if (!get_uint(&s, &stepf)) return fctret == NULL; } if (*s) return fctret == NULL; fct = new_fc_time(); if (!fct) { fprintf(stderr, "*** Out of memory\n"); exit(-1); } fct->fps = fps; fct->stepf = stepf; set_fc_time(fct, start, end); if (!fctexpect) { fctexpect = fct; } else { tail->next = fct; } tail = fct; } /* Compare the returned list against the expected one */ for (fct = fctexpect; fct; fct = fct->next) { if (verbose >= 2) { printf("\n[[%u:%u:%u.%u|%u - %u:%u:%u.%u|%u / %u @ %.1f]]" "\n<<%u:%u:%u.%u|%u - %u:%u:%u.%u|%u / %u @ (%d)>>\n", fct->sh, fct->sm, fct->ss, fct->sf, fct->stf, fct->eh, fct->em, fct->es, fct->ef, fct->etf, fct->stepf, fct->fps, fctret->sh, fctret->sm, fctret->ss, fctret->sf, fctret->stf, fctret->eh, fctret->em, fctret->es, fctret->ef, fctret->etf, fctret->stepf, (fctret->fps == fct->fps)); } if (!fctret || fctret->fps != fct->fps || fctret->stepf != fct->stepf || fctret->sh != fct->sh || fctret->sm != fct->sm || fctret->ss != fct->ss || fctret->sf != fct->sf || fctret->stf != fct->stf || fctret->eh != fct->eh || fctret->em != fct->em || fctret->es != fct->es || fctret->ef != fct->ef || fctret->etf != fct->etf ) { return 0; } fctret = fctret->next; } /* Everything succeeded */ return 1; } /*************************************************************************/ /*************************************************************************/ /* Helper macro to print a test name and result status, and set the * `failed' variable to nonzero if the test failed. Usage: * DO_TEST("name", test_function(param1,param2)); * The test function is assumed to return a true value for success, a * false value for failure (the return type does not matter). */ #define DO_TEST(name,test) do { \ if (verbose > 0) { \ printf("%s... ", (name)); \ fflush(stdout); \ } \ if (test) { \ if (verbose > 0) \ printf("ok\n"); \ } else { \ if (verbose > 0) \ printf("FAILED\n"); \ failed = 1; \ } \ } while (0) /* Shorthand for test_new_fc_time_from_string() */ #define DO_TEST_FC_STRING(str) \ DO_TEST("new_fc_time_from_string(" str ")", \ test_new_fc_time_from_string(str, ",", 10.0)) /*************************************************************************/ /* Main program. */ int main(int argc, char *argv[]) { int failed = 0; int opt; /* Option processing */ while ((opt = getopt(argc, argv, "hqv")) != EOF) { if (opt == 'q') { verbose = 0; } else if (opt == 'v') { verbose = 2; } else { fprintf(stderr, "Usage: %s [-q | -v]\n" "-q: quiet (don't print list of tests)\n" "-v: verbose (print debugging info)\n", argv[0]); return 1; } } /* Check that new_fc_time properly clears fields. */ DO_TEST("new_fc_time", test_new_fc_time()); /* Check that free_fc_time properly frees a multi-struct list. */ DO_TEST("free_fc_time", test_free_fc_time()); /* Check set_fc_time() using various frame ranges and fps. * First check simple frame counts within the first second; then * move on to values that require splitting between H/M/S/F; and * finally check that rounding of fractional frames (downward) is * performed correctly. */ DO_TEST("set_fc_time(0-1/10)", test_set_fc_time(0, 1, 10)); DO_TEST("set_fc_time(1-2/10)", test_set_fc_time(1, 2, 10)); DO_TEST("set_fc_time(0-10/10)", test_set_fc_time(0, 10, 10)); DO_TEST("set_fc_time(10-20/10)", test_set_fc_time(10, 20, 10)); DO_TEST("set_fc_time(0-600/10)", test_set_fc_time(0, 600, 10)); DO_TEST("set_fc_time(600-1200/10)", test_set_fc_time(600, 1200, 10)); DO_TEST("set_fc_time(0-36000/10)", test_set_fc_time(0, 36000, 10)); DO_TEST("set_fc_time(36000-72000/10)", test_set_fc_time(36000,72000,10)); DO_TEST("set_fc_time(0-37234/10)", test_set_fc_time(0, 37234, 10)); DO_TEST("set_fc_time(37234-74468/10)", test_set_fc_time(37234,74468,10)); DO_TEST("set_fc_time(0-10/8.8)", test_set_fc_time(0, 10, 8.8)); DO_TEST("set_fc_time(10-20/8.8)", test_set_fc_time(10, 20, 8.8)); DO_TEST("set_fc_time(0-10/8.2)", test_set_fc_time(0, 10, 8.2)); DO_TEST("set_fc_time(10-20/8.2)", test_set_fc_time(10, 20, 8.2)); /* Everything from here on down depends on set_fc_time() (and on * new_fc_time() in the case of new_fc_time_from_string()), so abort * now if we've failed somewhere. */ if (failed) { fprintf(stderr, "*** Aborting due to test failures.\n"); return 1; } /* Test various cases with fc_time_contains(): * A: 1 less than the starting frame in a large range * B: Equal to the starting frame in a large range * C: Midway between the starting and ending frames in a large range * D: 1 less than the ending frame in a large range * E: Equal to the ending frame in a large range * F: 1 less than the only frame in a 1-frame range * G: Equal to the only frame in a 1-frame range * H: 1 more than the only frame in a 1-frame range * for various types of lists: * 1: Only one fc_time in the list (first structure) * 2: The second of a list of 2 fc_times (last structure) * 3: The second of a list of 3 fc_times (middle structure) */ DO_TEST("fc_time_contains(1A)", test_fc_time_contains( 9, 10.0, 10, 20, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1B)", test_fc_time_contains(10, 10.0, 10, 20, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1C)", test_fc_time_contains(15, 10.0, 10, 20, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1D)", test_fc_time_contains(19, 10.0, 10, 20, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1E)", test_fc_time_contains(20, 10.0, 10, 20, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1F)", test_fc_time_contains( 9, 10.0, 10, 11, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1G)", test_fc_time_contains(10, 10.0, 10, 11, -1, -1, -1, -1)); DO_TEST("fc_time_contains(1H)", test_fc_time_contains(11, 10.0, 10, 11, -1, -1, -1, -1)); DO_TEST("fc_time_contains(2A)", test_fc_time_contains( 9, 10.0, 1, 2, 10, 20, -1, -1)); DO_TEST("fc_time_contains(2B)", test_fc_time_contains(10, 10.0, 1, 2, 10, 20, -1, -1)); DO_TEST("fc_time_contains(2C)", test_fc_time_contains(15, 10.0, 1, 2, 10, 20, -1, -1)); DO_TEST("fc_time_contains(2D)", test_fc_time_contains(19, 10.0, 1, 2, 10, 20, -1, -1)); DO_TEST("fc_time_contains(2E)", test_fc_time_contains(20, 10.0, 1, 2, 10, 20, -1, -1)); DO_TEST("fc_time_contains(2F)", test_fc_time_contains( 9, 10.0, 1, 2, 10, 11, -1, -1)); DO_TEST("fc_time_contains(2G)", test_fc_time_contains(10, 10.0, 1, 2, 10, 11, -1, -1)); DO_TEST("fc_time_contains(2H)", test_fc_time_contains(11, 10.0, 1, 2, 10, 11, -1, -1)); DO_TEST("fc_time_contains(3A)", test_fc_time_contains( 9, 10.0, 1, 2, 10, 20, 30, 40)); DO_TEST("fc_time_contains(3B)", test_fc_time_contains(10, 10.0, 1, 2, 10, 20, 30, 40)); DO_TEST("fc_time_contains(3C)", test_fc_time_contains(15, 10.0, 1, 2, 10, 20, 30, 40)); DO_TEST("fc_time_contains(3D)", test_fc_time_contains(19, 10.0, 1, 2, 10, 20, 30, 40)); DO_TEST("fc_time_contains(3E)", test_fc_time_contains(20, 10.0, 1, 2, 10, 20, 30, 40)); DO_TEST("fc_time_contains(3F)", test_fc_time_contains( 9, 10.0, 1, 2, 10, 11, 30, 40)); DO_TEST("fc_time_contains(3G)", test_fc_time_contains(10, 10.0, 1, 2, 10, 11, 30, 40)); DO_TEST("fc_time_contains(3H)", test_fc_time_contains(11, 10.0, 1, 2, 10, 11, 30, 40)); /* See whether new_fc_time_from_string() works with a simple string */ DO_TEST_FC_STRING("10-20"); DO_TEST_FC_STRING("10-20/3"); /* Try some invalid variations */ DO_TEST_FC_STRING("10-"); DO_TEST_FC_STRING("-20"); DO_TEST_FC_STRING("10-20/"); DO_TEST_FC_STRING("a-20"); DO_TEST_FC_STRING("10-b"); DO_TEST_FC_STRING("10a-20"); DO_TEST_FC_STRING("10-20b"); DO_TEST_FC_STRING("10-20/c"); DO_TEST_FC_STRING("10-20/30c"); /* Try with multiple entries */ DO_TEST_FC_STRING("10-20,30-40"); DO_TEST_FC_STRING(",10-20,,30-40,"); // extra commas should be ignored DO_TEST_FC_STRING("10-20,30-40/5,60-70"); DO_TEST_FC_STRING("10-20,30-40b,50-60"); /* Try timecodes instead of frames */ DO_TEST_FC_STRING("1.0-20"); DO_TEST_FC_STRING("10-2.0"); DO_TEST_FC_STRING("1:1-2000"); DO_TEST_FC_STRING("1-2:2"); DO_TEST_FC_STRING("1:08-2000"); // make sure it doesn't try to parse octal DO_TEST_FC_STRING("10-2:08"); DO_TEST_FC_STRING("1:1:1-200000"); DO_TEST_FC_STRING("10-2:2:2"); DO_TEST_FC_STRING("1:1:1.1-200000"); DO_TEST_FC_STRING("10-2:2:2.2"); DO_TEST_FC_STRING("1:1:1.1-200000/3"); DO_TEST_FC_STRING("10-2:2:2.2/3"); /* Test invalid timecodes as well */ DO_TEST_FC_STRING("1:1:1:1-200000"); DO_TEST_FC_STRING("10-2:2:2:2"); DO_TEST_FC_STRING("1.1.1-200000"); DO_TEST_FC_STRING("10-2.2.2"); DO_TEST_FC_STRING("1:1:1.1.1-200000"); DO_TEST_FC_STRING("10-2:2:2.2.2"); /* All done, exit with appropriate status */ return failed ? 1 : 0; } /*************************************************************************/ /* * Local variables: * c-file-style: "stroustrup" * c-file-offsets: ((case-label . *) (statement-case-intro . *)) * indent-tabs-mode: nil * End: * * vim: expandtab shiftwidth=4: */