/* liblouis Braille Translation and Back-Translation Library Copyright (C) 2015, 2016 Christian Egli, Swiss Library for the Blind, Visually Impaired and Print Disabled Copyright (C) 2017 Bert Frees This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "internal.h" #include "error.h" #include "errno.h" #include "progname.h" #include "version-etc.h" #include "brl_checks.h" static int verbose = 0; static const struct option longopts[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "verbose", no_argument, &verbose, 1 }, { NULL, 0, NULL, 0 }, }; const char version_etc_copyright[] = "Copyright %s %d Swiss Library for the Blind, Visually Impaired and Print " "Disabled"; #define AUTHORS "Christian Egli" #define DIRECTION_FORWARD 0 #define DIRECTION_BACKWARD 1 #define DIRECTION_BOTH 2 #define DIRECTION_DEFAULT DIRECTION_FORWARD #define HYPHENATION_OFF 0 #define HYPHENATION_ON 1 #define HYPHENATION_BRAILLE 2 #define HYPHENATION_DEFAULT HYPHENATION_OFF static void print_help(void) { printf("\ Usage: %s YAML_TEST_FILE\n", program_name); fputs("\ Run the tests defined in the YAML_TEST_FILE. Return 0 if all tests pass\n\ or 1 if any of the tests fail. The details of failing tests are printed\n\ to stderr.\n\n", stdout); fputs("\ -h, --help display this help and exit\n\ -v, --version display version information and exit\n\ --verbose report expected failures\n", stdout); printf("\n"); printf("Report bugs to %s.\n", PACKAGE_BUGREPORT); #ifdef PACKAGE_PACKAGER_BUG_REPORTS printf("Report %s bugs to: %s\n", PACKAGE_PACKAGER, PACKAGE_PACKAGER_BUG_REPORTS); #endif #ifdef PACKAGE_URL printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL); #endif } #define EXIT_SKIPPED 77 #ifdef HAVE_LIBYAML #include typedef struct { const char *name; const char *content; // table content in case of an inline table; NULL means name is // a file int location; // location in YAML file (line number) where table is defined int is_display; // whether the table is a display table or a translation table } table_value; const char *event_names[] = { "YAML_NO_EVENT", "YAML_STREAM_START_EVENT", "YAML_STREAM_END_EVENT", "YAML_DOCUMENT_START_EVENT", "YAML_DOCUMENT_END_EVENT", "YAML_ALIAS_EVENT", "YAML_SCALAR_EVENT", "YAML_SEQUENCE_START_EVENT", "YAML_SEQUENCE_END_EVENT", "YAML_MAPPING_START_EVENT", "YAML_MAPPING_END_EVENT" }; const char *encoding_names[] = { "YAML_ANY_ENCODING", "YAML_UTF8_ENCODING", "YAML_UTF16LE_ENCODING", "YAML_UTF16BE_ENCODING" }; const char *inline_table_prefix = "checkyaml_inline_table_at_line_"; char *file_name; int errors = 0; int count = 0; static char const **emph_classes = NULL; static void simple_error(const char *msg, yaml_parser_t *parser, yaml_event_t *event) { error_at_line(EXIT_FAILURE, 0, file_name, event->start_mark.line ? event->start_mark.line + 1 : parser->problem_mark.line + 1, "%s", msg); } static void yaml_parse_error(yaml_parser_t *parser) { error_at_line(EXIT_FAILURE, 0, file_name, parser->problem_mark.line + 1, "%s", parser->problem); } static void yaml_error(yaml_event_type_t expected, yaml_event_t *event) { error_at_line(EXIT_FAILURE, 0, file_name, event->start_mark.line + 1, "Expected %s (actual %s)", event_names[expected], event_names[event->type]); } static char * read_table_query(yaml_parser_t *parser, const char **table_file_name_check) { yaml_event_t event; char *query_as_string = malloc(sizeof(char) * MAXSTRING); char *p = query_as_string; query_as_string[0] = '\0'; while (1) { if (!yaml_parser_parse(parser, &event)) yaml_error(YAML_SCALAR_EVENT, &event); if (event.type == YAML_SCALAR_EVENT) { // (temporary) feature to check whether the table query matches an expected // table if (!strcmp((const char *)event.data.scalar.value, "__assert-match")) { yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); *table_file_name_check = strdup((const char *)event.data.scalar.value); yaml_event_delete(&event); } else { if (query_as_string != p) strcat(p++, " "); strcat(p, (const char *)event.data.scalar.value); p += event.data.scalar.length; strcat(p++, ":"); yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); strcat(p, (const char *)event.data.scalar.value); p += event.data.scalar.length; yaml_event_delete(&event); } } else if (event.type == YAML_MAPPING_END_EVENT) { yaml_event_delete(&event); break; } else yaml_error(YAML_SCALAR_EVENT, &event); } return query_as_string; } static void compile_inline_table(const table_value *table) { int location = table->location; char *p = (char *)table->content; char *line_start = p; int line_len = 0; while (*p) { if (*p == 10 || *p == 13) { char *line = strndup((const char *)line_start, line_len); int error = 0; if (!table->is_display) error = !_lou_compileTranslationRule(table->name, line); else error = !_lou_compileDisplayRule(table->name, line); if (error) error_at_line(EXIT_FAILURE, 0, file_name, location, "Error in table %s", table->name); location++; free(line); line_start = p + 1; line_len = 0; } else { line_len++; } p++; } } static table_value * read_table_value(yaml_parser_t *parser, int start_line, int is_display) { table_value *table; char *table_name = malloc(sizeof(char) * MAXSTRING); char *table_content = NULL; yaml_event_t event; table_name[0] = '\0'; if (!yaml_parser_parse(parser, &event) || !(event.type == YAML_SEQUENCE_START_EVENT || event.type == YAML_SCALAR_EVENT || event.type == YAML_MAPPING_START_EVENT)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected %s, %s or %s (actual %s)", event_names[YAML_SEQUENCE_START_EVENT], event_names[YAML_SCALAR_EVENT], event_names[YAML_MAPPING_START_EVENT], event_names[event.type]); if (event.type == YAML_SEQUENCE_START_EVENT) { yaml_event_delete(&event); int done = 0; char *p = table_name; while (!done) { if (!yaml_parser_parse(parser, &event)) { yaml_parse_error(parser); } if (event.type == YAML_SEQUENCE_END_EVENT) { done = 1; } else if (event.type == YAML_SCALAR_EVENT) { if (table_name != p) strcat(p++, ","); strcat(p, (const char *)event.data.scalar.value); p += event.data.scalar.length; } yaml_event_delete(&event); } } else if (event.type == YAML_SCALAR_EVENT) { yaml_char_t *p = event.data.scalar.value; if (*p) while (p[1]) p++; if (*p == 10 || *p == 13) { // If the scalar ends with a newline, assume it is a block // scalar, so treat as an inline table. (Is there a proper way // to check for a block scalar?) sprintf(table_name, "%s%d", inline_table_prefix, start_line); table_content = strdup((const char *)event.data.scalar.value); } else { strcat(table_name, (const char *)event.data.scalar.value); } yaml_event_delete(&event); } else { // event.type == YAML_MAPPING_START_EVENT char *query; const char *table_file_name_check = NULL; yaml_event_delete(&event); query = read_table_query(parser, &table_file_name_check); table_name = lou_findTable(query); free(query); if (!table_name) error_at_line(EXIT_FAILURE, 0, file_name, start_line, "Table query did not match a table"); if (table_file_name_check) { const char *table_file_name = table_name; do { table_file_name++; } while (*table_file_name); while (table_file_name >= table_name && *table_file_name != '/' && *table_file_name != '\\') table_file_name--; if (strcmp(table_file_name_check, table_file_name + 1)) error_at_line(EXIT_FAILURE, 0, file_name, start_line, "Table query did not match expected table: expected '%s' but got " "'%s'", table_file_name_check, table_file_name + 1); } } table = malloc(sizeof(table_value)); table->name = table_name; table->content = table_content; table->location = start_line + 1; table->is_display = is_display; return table; } static void free_table_value(table_value *table) { if (table) { free((char *)table->name); if (table->content) free((char *)table->content); free(table); } } static char * read_table(yaml_event_t *start_event, yaml_parser_t *parser, const char *display_table) { table_value *v = NULL; char *table_name = NULL; if (start_event->type != YAML_SCALAR_EVENT || strcmp((const char *)start_event->data.scalar.value, "table")) return 0; v = read_table_value(parser, start_event->start_mark.line + 1, 0); if (!_lou_getTranslationTable(v->name)) error_at_line(EXIT_FAILURE, 0, file_name, start_event->start_mark.line + 1, "Table %s not valid", v->name); if (v->content) compile_inline_table(v); emph_classes = lou_getEmphClasses(v->name); // get declared emphasis classes table_name = strdup((char *)v->name); if (!display_table) { if (!_lou_getDisplayTable(v->name)) error_at_line(EXIT_FAILURE, 0, file_name, start_event->start_mark.line + 1, "Table %s not valid", v->name); if (v->content) { v->is_display = 1; compile_inline_table(v); } } free_table_value(v); return table_name; } static void read_flags(yaml_parser_t *parser, int *direction, int *hyphenation) { yaml_event_t event; int parse_error = 1; *direction = DIRECTION_DEFAULT; *hyphenation = HYPHENATION_DEFAULT; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) yaml_error(YAML_MAPPING_START_EVENT, &event); yaml_event_delete(&event); while ((parse_error = yaml_parser_parse(parser, &event)) && (event.type == YAML_SCALAR_EVENT)) { if (!strcmp((const char *)event.data.scalar.value, "testmode")) { yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); if (!strcmp((const char *)event.data.scalar.value, "forward")) { *direction = DIRECTION_FORWARD; } else if (!strcmp((const char *)event.data.scalar.value, "backward")) { *direction = DIRECTION_BACKWARD; } else if (!strcmp((const char *)event.data.scalar.value, "bothDirections")) { *direction = DIRECTION_BOTH; } else if (!strcmp((const char *)event.data.scalar.value, "hyphenate")) { *hyphenation = HYPHENATION_ON; } else if (!strcmp((const char *)event.data.scalar.value, "hyphenateBraille")) { *hyphenation = HYPHENATION_BRAILLE; } else { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Testmode '%s' not supported\n", event.data.scalar.value); } } else { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Flag '%s' not supported\n", event.data.scalar.value); } } if (!parse_error) yaml_parse_error(parser); if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); yaml_event_delete(&event); } static int read_xfail(yaml_parser_t *parser) { yaml_event_t event; /* assume xfail true if there is an xfail key */ int xfail = 1; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); if (!strcmp((const char *)event.data.scalar.value, "false") || !strcmp((const char *)event.data.scalar.value, "off")) xfail = 0; yaml_event_delete(&event); return xfail; } static translationModes read_mode(yaml_parser_t *parser) { yaml_event_t event; translationModes mode = 0; int parse_error = 1; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) yaml_error(YAML_SEQUENCE_START_EVENT, &event); yaml_event_delete(&event); while ((parse_error = yaml_parser_parse(parser, &event)) && (event.type == YAML_SCALAR_EVENT)) { if (!strcmp((const char *)event.data.scalar.value, "noContractions")) { mode |= noContractions; } else if (!strcmp((const char *)event.data.scalar.value, "compbrlAtCursor")) { mode |= compbrlAtCursor; } else if (!strcmp((const char *)event.data.scalar.value, "dotsIO")) { mode |= dotsIO; } else if (!strcmp((const char *)event.data.scalar.value, "compbrlLeftCursor")) { mode |= compbrlLeftCursor; } else if (!strcmp((const char *)event.data.scalar.value, "ucBrl")) { mode |= ucBrl; } else if (!strcmp((const char *)event.data.scalar.value, "noUndefined")) { mode |= noUndefined; } else if (!strcmp((const char *)event.data.scalar.value, "partialTrans")) { mode |= partialTrans; } else { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Mode '%s' not supported\n", event.data.scalar.value); } yaml_event_delete(&event); } if (!parse_error) yaml_parse_error(parser); if (event.type != YAML_SEQUENCE_END_EVENT) yaml_error(YAML_SEQUENCE_END_EVENT, &event); yaml_event_delete(&event); return mode; } static int parse_number(const char *number, const char *name, int file_line) { char *tail; errno = 0; int val = strtol(number, &tail, 0); if (errno != 0) error_at_line(EXIT_FAILURE, 0, file_name, file_line, "Not a valid %s '%s'. Must be a number\n", name, number); if (number == tail) error_at_line(EXIT_FAILURE, 0, file_name, file_line, "No digits found in %s '%s'. Must be a number\n", name, number); return val; } static int * read_inPos(yaml_parser_t *parser, int translen) { int *pos = malloc(sizeof(int) * translen); int i = 0; yaml_event_t event; int parse_error = 1; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) yaml_error(YAML_SEQUENCE_START_EVENT, &event); yaml_event_delete(&event); while ((parse_error = yaml_parser_parse(parser, &event)) && (event.type == YAML_SCALAR_EVENT)) { if (i >= translen) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too many input positions for translation of length %d.", translen); pos[i++] = parse_number((const char *)event.data.scalar.value, "input position", event.start_mark.line + 1); yaml_event_delete(&event); } if (i < translen) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too few input positions (%i) for translation of length %i\n", i, translen); if (!parse_error) yaml_parse_error(parser); if (event.type != YAML_SEQUENCE_END_EVENT) yaml_error(YAML_SEQUENCE_END_EVENT, &event); yaml_event_delete(&event); return pos; } static int * read_outPos(yaml_parser_t *parser, int wrdlen, int translen) { int *pos = malloc(sizeof(int) * wrdlen); int i = 0; yaml_event_t event; int parse_error = 1; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) yaml_error(YAML_SEQUENCE_START_EVENT, &event); yaml_event_delete(&event); while ((parse_error = yaml_parser_parse(parser, &event)) && (event.type == YAML_SCALAR_EVENT)) { if (i >= wrdlen) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too many output positions for input string of length %d.", translen); pos[i++] = parse_number((const char *)event.data.scalar.value, "output position", event.start_mark.line + 1); yaml_event_delete(&event); } if (i < wrdlen) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too few output positions (%i) for input string of length %i\n", i, wrdlen); if (!parse_error) yaml_parse_error(parser); if (event.type != YAML_SEQUENCE_END_EVENT) yaml_error(YAML_SEQUENCE_END_EVENT, &event); yaml_event_delete(&event); return pos; } static void read_cursorPos(yaml_parser_t *parser, int *cursorPos, int *expected_cursorPos, int wrdlen, int translen) { yaml_event_t event; if (!yaml_parser_parse(parser, &event) || !(event.type == YAML_SEQUENCE_START_EVENT || event.type == YAML_SCALAR_EVENT)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected %s or %s (actual %s)", event_names[YAML_SEQUENCE_START_EVENT], event_names[YAML_SCALAR_EVENT], event_names[event.type]); if (event.type == YAML_SEQUENCE_START_EVENT) { /* it's a sequence: read the two cursor positions (before and after) */ yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); *cursorPos = parse_number((const char *)event.data.scalar.value, "cursor position", event.start_mark.line + 1); if ((0 > *cursorPos) || (*cursorPos >= wrdlen)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Cursor position (%i) outside of input string of length %i\n", *cursorPos, wrdlen); yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too few cursor positions, 2 are expected (before and after)\n"); *expected_cursorPos = parse_number((const char *)event.data.scalar.value, "expected cursor position", event.start_mark.line + 1); if ((0 > *expected_cursorPos) || (*expected_cursorPos >= wrdlen)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected cursor position (%i) outside of output string of length " "%i\n", *expected_cursorPos, translen); yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_END_EVENT)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too many cursor positions, only 2 are expected (before and " "after)\n"); yaml_event_delete(&event); } else { // YAML_SCALAR_EVENT /* it's just a single value: just read the initial cursor position */ *cursorPos = parse_number((const char *)event.data.scalar.value, "cursor position before", event.start_mark.line + 1); if ((0 > *cursorPos) || (*cursorPos >= wrdlen)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Cursor position (%i) outside of input string of length %i\n", *cursorPos, wrdlen); *expected_cursorPos = -1; yaml_event_delete(&event); } } static void read_typeform_string(yaml_parser_t *parser, formtype *typeform, typeforms kind, int len) { yaml_event_t event; int typeform_len; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); typeform_len = strlen((const char *)event.data.scalar.value); if (typeform_len != len) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Too many or too few typeforms (%i) for word of length %i\n", typeform_len, len); update_typeform((const char *)event.data.scalar.value, typeform, kind); yaml_event_delete(&event); } static formtype * read_typeforms(yaml_parser_t *parser, int len) { yaml_event_t event; formtype *typeform = calloc(len, sizeof(formtype)); int parse_error = 1; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) yaml_error(YAML_MAPPING_START_EVENT, &event); yaml_event_delete(&event); while ((parse_error = yaml_parser_parse(parser, &event)) && (event.type == YAML_SCALAR_EVENT)) { if (strcmp((const char *)event.data.scalar.value, "computer_braille") == 0) { yaml_event_delete(&event); read_typeform_string(parser, typeform, computer_braille, len); } else if (strcmp((const char *)event.data.scalar.value, "no_translate") == 0) { yaml_event_delete(&event); read_typeform_string(parser, typeform, no_translate, len); } else if (strcmp((const char *)event.data.scalar.value, "no_contract") == 0) { yaml_event_delete(&event); read_typeform_string(parser, typeform, no_contract, len); } else { int i; typeforms kind = plain_text; for (i = 0; emph_classes[i]; i++) { if (strcmp((const char *)event.data.scalar.value, emph_classes[i]) == 0) { yaml_event_delete(&event); kind = italic << i; if (kind > emph_10) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Typeform '%s' was not declared\n", event.data.scalar.value); read_typeform_string(parser, typeform, kind, len); break; } } } } if (!parse_error) yaml_parse_error(parser); if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); yaml_event_delete(&event); return typeform; } static void read_options(yaml_parser_t *parser, int direction, int wordLen, int translationLen, int *xfail, translationModes *mode, formtype **typeform, int **inPos, int **outPos, int *cursorPos, int *cursorOutPos, int *maxOutputLen, int *realInputLen) { yaml_event_t event; char *option_name; int parse_error = 1; *mode = 0; *xfail = 0; *typeform = NULL; *inPos = NULL; *outPos = NULL; while ((parse_error = yaml_parser_parse(parser, &event)) && (event.type == YAML_SCALAR_EVENT)) { option_name = strndup((const char *)event.data.scalar.value, event.data.scalar.length); if (!strcmp(option_name, "xfail")) { yaml_event_delete(&event); *xfail = read_xfail(parser); } else if (!strcmp(option_name, "mode")) { yaml_event_delete(&event); *mode = read_mode(parser); } else if (!strcmp(option_name, "typeform")) { if (direction != 0) { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "typeforms only supported with testmode 'forward'\n"); } yaml_event_delete(&event); *typeform = read_typeforms(parser, wordLen); } else if (!strcmp(option_name, "inputPos")) { yaml_event_delete(&event); *inPos = read_inPos(parser, translationLen); } else if (!strcmp(option_name, "outputPos")) { yaml_event_delete(&event); *outPos = read_outPos(parser, wordLen, translationLen); } else if (!strcmp(option_name, "cursorPos")) { if (direction == 2) { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "cursorPos not supported with testmode 'bothDirections'\n"); } yaml_event_delete(&event); read_cursorPos(parser, cursorPos, cursorOutPos, wordLen, translationLen); } else if (!strcmp(option_name, "maxOutputLength")) { if (direction == 2) { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "maxOutputLength not supported with testmode 'bothDirections'\n"); } yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); *maxOutputLen = parse_number((const char *)event.data.scalar.value, "Maximum output length", event.start_mark.line + 1); if (*maxOutputLen <= 0) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Maximum output length (%i) must be a positive number\n", *maxOutputLen); if (*maxOutputLen < translationLen) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected translation length (%i) must not exceed maximum output " "length (%i)\n", translationLen, *maxOutputLen); yaml_event_delete(&event); } else if (!strcmp(option_name, "realInputLength")) { yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) yaml_error(YAML_SCALAR_EVENT, &event); *realInputLen = parse_number((const char *)event.data.scalar.value, "Real input length", event.start_mark.line + 1); if (*realInputLen < 0) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Real input length (%i) must not be a negative number\n", *realInputLen); if (*realInputLen > wordLen) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Real input length (%i) must not exceed total input " "length (%i)\n", *realInputLen, wordLen); yaml_event_delete(&event); } else { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Unsupported option %s", option_name); } free(option_name); } if (!parse_error) yaml_parse_error(parser); if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); yaml_event_delete(&event); } /* see http://stackoverflow.com/questions/5117393/utf-8-strings-length-in-linux-c */ static int my_strlen_utf8_c(char *s) { int i = 0, j = 0; while (s[i]) { if ((s[i] & 0xc0) != 0x80) j++; i++; } return j; } /* * String parsing is also done later in check_base. At this point we * only need it to compute the actual string length in order to be * able to provide error messages when parsing typeform and position arrays. */ static int parsed_strlen(char *s) { widechar *buf; int len, maxlen; maxlen = my_strlen_utf8_c(s); buf = malloc(sizeof(widechar) * maxlen); len = _lou_extParseChars(s, buf); free(buf); return len; } static void read_test(yaml_parser_t *parser, char **tables, const char *display_table, int direction, int hyphenation) { yaml_event_t event; char *description = NULL; char *word; char *translation; int xfail = 0; translationModes mode = 0; formtype *typeform = NULL; int *inPos = NULL; int *outPos = NULL; int cursorPos = -1; int cursorOutPos = -1; int maxOutputLen = -1; int realInputLen = -1; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) simple_error("Word expected", parser, &event); word = strndup((const char *)event.data.scalar.value, event.data.scalar.length); yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) simple_error("Translation expected", parser, &event); translation = strndup((const char *)event.data.scalar.value, event.data.scalar.length); yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event)) yaml_parse_error(parser); /* Handle an optional description */ if (event.type == YAML_SCALAR_EVENT) { description = word; word = translation; translation = strndup((const char *)event.data.scalar.value, event.data.scalar.length); yaml_event_delete(&event); if (!yaml_parser_parse(parser, &event)) yaml_parse_error(parser); } if (event.type == YAML_MAPPING_START_EVENT) { yaml_event_delete(&event); read_options(parser, direction, parsed_strlen(word), parsed_strlen(translation), &xfail, &mode, &typeform, &inPos, &outPos, &cursorPos, &cursorOutPos, &maxOutputLen, &realInputLen); if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_END_EVENT)) yaml_error(YAML_SEQUENCE_END_EVENT, &event); } else if (event.type != YAML_SEQUENCE_END_EVENT) { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected %s or %s (actual %s)", event_names[YAML_MAPPING_START_EVENT], event_names[YAML_SEQUENCE_END_EVENT], event_names[event.type]); } int result = 0; char **table = tables; while (*table) { int r; if (hyphenation == HYPHENATION_ON || hyphenation == HYPHENATION_BRAILLE) { r = check_hyphenation( *table, word, translation, hyphenation == HYPHENATION_BRAILLE); } else { // FIXME: Note that the typeform array was constructed using the // emphasis classes mapping of the last compiled table. This // means that if we are testing multiple tables at the same time // they must have the same mapping (i.e. the emphasis classes // must be defined in the same order). r = check(*table, word, translation, .display_table = display_table, .typeform = typeform, .mode = mode, .expected_inputPos = inPos, .expected_outputPos = outPos, .cursorPos = cursorPos, .expected_cursorPos = cursorOutPos, .max_outlen = maxOutputLen, .real_inlen = realInputLen, .direction = direction, .diagnostics = !xfail); } if (xfail != r) { // FAIL or XPASS if (description) fprintf(stderr, "%s\n", description); error_at_line(0, 0, file_name, event.start_mark.line + 1, (xfail ? "Unexpected Pass" : "Failure")); errors++; // on error print the table name, as it isn't always clear // which table we are testing. You can can define a test // for multiple tables. fprintf(stderr, "Table: %s\n", *table); if (display_table) fprintf(stderr, "Display table: %s\n", display_table); // add an empty line after each error fprintf(stderr, "\n"); } else if (xfail && r && verbose) { // XFAIL // in verbose mode print expected failures if (description) fprintf(stderr, "%s\n", description); error_at_line(0, 0, file_name, event.start_mark.line + 1, "Expected Failure"); fprintf(stderr, "Table: %s\n", *table); if (display_table) fprintf(stderr, "Display table: %s\n", display_table); fprintf(stderr, "\n"); } result |= r; table++; count++; } yaml_event_delete(&event); free(description); free(word); free(translation); free(typeform); free(inPos); free(outPos); } static void read_tests(yaml_parser_t *parser, char **tables, const char *display_table, int direction, int hyphenation) { yaml_event_t event; if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) yaml_error(YAML_SEQUENCE_START_EVENT, &event); yaml_event_delete(&event); int done = 0; while (!done) { if (!yaml_parser_parse(parser, &event)) { yaml_parse_error(parser); } if (event.type == YAML_SEQUENCE_END_EVENT) { done = 1; yaml_event_delete(&event); } else if (event.type == YAML_SEQUENCE_START_EVENT) { yaml_event_delete(&event); read_test(parser, tables, display_table, direction, hyphenation); } else { error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected %s or %s (actual %s)", event_names[YAML_SEQUENCE_END_EVENT], event_names[YAML_SEQUENCE_START_EVENT], event_names[event.type]); } } } /* * This custom table resolver handles magic table names that represent * inline tables. */ static char ** customTableResolver(const char *tableList, const char *base) { static char *dummy_table[1]; char *p = (char *)tableList; while (*p != '\0') { if (strncmp(p, inline_table_prefix, strlen(inline_table_prefix)) == 0) return dummy_table; while (*p != '\0' && *p != ',') p++; if (*p == ',') p++; } return _lou_defaultTableResolver(tableList, base); } #endif // HAVE_LIBYAML int main(int argc, char *argv[]) { int optc; set_program_name(argv[0]); while ((optc = getopt_long(argc, argv, "hv", longopts, NULL)) != -1) switch (optc) { /* --help and --version exit immediately, per GNU coding standards. */ case 'v': version_etc( stdout, program_name, PACKAGE_NAME, VERSION, AUTHORS, (char *)NULL); exit(EXIT_SUCCESS); break; case 'h': print_help(); exit(EXIT_SUCCESS); break; default: fprintf(stderr, "Try `%s --help' for more information.\n", program_name); exit(EXIT_FAILURE); break; } if (optind != argc - 1) { /* Print error message and exit. */ if (optind < argc - 1) fprintf(stderr, "%s: extra operand: %s\n", program_name, argv[optind + 1]); else fprintf(stderr, "%s: no YAML test file specified\n", program_name); fprintf(stderr, "Try `%s --help' for more information.\n", program_name); exit(EXIT_FAILURE); } #ifdef WITHOUT_YAML fprintf(stderr, "Skipping tests for %s as yaml was disabled in configure with " "--without-yaml\n", argv[1]); return EXIT_SKIPPED; #else #ifndef HAVE_LIBYAML fprintf(stderr, "Skipping tests for %s as libyaml was not found\n", argv[1]); return EXIT_SKIPPED; #endif // not HAVE_LIBYAML #endif // WITHOUT_YAML #ifndef WITHOUT_YAML #ifdef HAVE_LIBYAML FILE *file; yaml_parser_t parser; yaml_event_t event; file_name = argv[1]; file = fopen(file_name, "rb"); if (!file) { fprintf(stderr, "%s: file not found: %s\n", program_name, file_name); exit(3); } char *dir_name = strdup(file_name); int i = strlen(dir_name); while (i > 0) { if (dir_name[i - 1] == '/' || dir_name[i - 1] == '\\') { i--; break; } i--; } dir_name[i] = '\0'; // FIXME: problem with this is that // LOUIS_TABLEPATH=$(top_srcdir)/tables,... does not work anymore because // $(top_srcdir) == .. (not an absolute path) if (i > 0) if (chdir(dir_name)) error(EXIT_FAILURE, EIO, "Cannot change directory to %s", dir_name); // register custom table resolver lou_registerTableResolver(&customTableResolver); assert(yaml_parser_initialize(&parser)); yaml_parser_set_input_file(&parser, file); if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_STREAM_START_EVENT)) { yaml_error(YAML_STREAM_START_EVENT, &event); } if (event.data.stream_start.encoding != YAML_UTF8_ENCODING) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "UTF-8 encoding expected (actual %s)", encoding_names[event.data.stream_start.encoding]); yaml_event_delete(&event); if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_DOCUMENT_START_EVENT)) { yaml_error(YAML_DOCUMENT_START_EVENT, &event); } yaml_event_delete(&event); if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) { yaml_error(YAML_MAPPING_START_EVENT, &event); } yaml_event_delete(&event); if (!yaml_parser_parse(&parser, &event)) simple_error("table expected", &parser, &event); int MAXTABLES = 150; char *tables[MAXTABLES + 1]; char *display_table = NULL; while (1) { if (event.type == YAML_SCALAR_EVENT && !strcmp((const char *)event.data.scalar.value, "display")) { table_value *v; free(display_table); v = read_table_value(&parser, event.start_mark.line + 1, 1); display_table = strdup((char *)v->name); if (!_lou_getDisplayTable(display_table)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Display table %s not valid", display_table); if (v->content) compile_inline_table(v); free_table_value(v); yaml_event_delete(&event); if (!yaml_parser_parse(&parser, &event)) simple_error("table expected", &parser, &event); } if (!(tables[0] = read_table(&event, &parser, display_table))) break; yaml_event_delete(&event); int k = 1; while (1) { if (!yaml_parser_parse(&parser, &event)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected table or %s (actual %s)", event_names[YAML_SCALAR_EVENT], event_names[event.type]); if ((tables[k++] = read_table(&event, &parser, display_table))) { if (k == MAXTABLES) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Only %d tables in one YAML test supported", MAXTABLES); yaml_event_delete(&event); } else break; } if (event.type != YAML_SCALAR_EVENT) yaml_error(YAML_SCALAR_EVENT, &event); int haveRunTests = 0; while (1) { int direction = DIRECTION_DEFAULT; int hyphenation = HYPHENATION_DEFAULT; if (!strcmp((const char *)event.data.scalar.value, "flags")) { yaml_event_delete(&event); read_flags(&parser, &direction, &hyphenation); if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_SCALAR_EVENT) || strcmp((const char *)event.data.scalar.value, "tests")) { simple_error("tests expected", &parser, &event); } yaml_event_delete(&event); read_tests(&parser, tables, display_table, direction, hyphenation); haveRunTests = 1; } else if (!strcmp((const char *)event.data.scalar.value, "tests")) { yaml_event_delete(&event); read_tests(&parser, tables, display_table, direction, hyphenation); haveRunTests = 1; } else { if (haveRunTests) { break; } else { simple_error("flags or tests expected", &parser, &event); } } if (!yaml_parser_parse(&parser, &event)) error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, "Expected table, flags, tests or %s (actual %s)", event_names[YAML_MAPPING_END_EVENT], event_names[event.type]); if (event.type != YAML_SCALAR_EVENT) break; } char **p = tables; while (*p) free(*(p++)); } if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); yaml_event_delete(&event); if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_DOCUMENT_END_EVENT)) { yaml_error(YAML_DOCUMENT_END_EVENT, &event); } yaml_event_delete(&event); if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_STREAM_END_EVENT)) { yaml_error(YAML_STREAM_END_EVENT, &event); } yaml_event_delete(&event); yaml_parser_delete(&parser); free(emph_classes); free(display_table); lou_free(); assert(!fclose(file)); printf("%s (%d tests, %d failure%s)\n", (errors ? "FAILURE" : "SUCCESS"), count, errors, ((errors != 1) ? "s" : "")); return errors ? 1 : 0; #endif // HAVE_LIBYAML #endif // not WITHOUT_YAML }