/* liblouis Braille Translation and Back-Translation Library Copyright (C) 2008 Eitan Isaacson Copyright (C) 2012 James Teh Copyright (C) 2012 Bert Frees Copyright (C) 2014 Mesar Hameed Copyright (C) 2015 Mike Gray Copyright (C) 2010-2017 Swiss Library for the Blind, Visually Impaired and Print Disabled Copyright (C) 2016-2017 Davy Kager Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. */ /** * @file * @brief Test helper functions */ #include #include #include #include #include #include "liblouis.h" #include "internal.h" #include "brl_checks.h" #include "unistr.h" static void print_int_array(const char *prefix, int *pos_list, int len) { int i; fprintf(stderr, "%s ", prefix); for (i = 0; i < len; i++) fprintf(stderr, "%d ", pos_list[i]); fprintf(stderr, "\n"); } static void print_typeform(const formtype *typeform, int len) { int i; fprintf(stderr, "Typeform: "); for (i = 0; i < len; i++) fprintf(stderr, "%hi", typeform[i]); fprintf(stderr, "\n"); } static void print_widechars(widechar *buffer, int length) { uint8_t *result_buf; size_t result_len; #ifdef WIDECHARS_ARE_UCS4 result_buf = u32_to_u8(buffer, length, NULL, &result_len); #else result_buf = u16_to_u8(buffer, length, NULL, &result_len); #endif fprintf(stderr, "%.*s", (int)result_len, result_buf); free(result_buf); } /* direction, 0=forward, 1=backwards, 2=both directions. If diagnostics is 1 then * print diagnostics in case where the translation is not as * expected */ int check_base(const char *tableList, const char *input, const char *expected, optional_test_params in) { int i, retval = 0; int direction = in.direction; const int *expected_inputPos = in.expected_inputPos; const int *expected_outputPos = in.expected_outputPos; if (in.direction < 0 || in.direction > 2) { fprintf(stderr, "Invalid direction.\n"); return 1; } if (in.direction != 0 && in.typeform != NULL) { // Currently, in backward translation, nothing is done with the initial value of // the typeform argument, and on return it always contains all zeros, so it // doesn't make any sense to use typeforms in backward translation tests. fprintf(stderr, "typeforms only supported with testmode 'forward'\n"); return 1; } if (in.direction == 2 && in.cursorPos >= 0) { fprintf(stderr, "cursorPos not supported with testmode 'bothDirections'\n"); return 1; } if (in.direction == 2 && in.max_outlen >= 0) { fprintf(stderr, "maxOutputLength not supported with testmode 'bothDirections'\n"); return 1; } if (in.real_inlen >= 0 && in.max_outlen < 0) { fprintf(stderr, "realInputLength not supported when maxOutputLength is not specified\n"); return 1; } while (1) { widechar *inbuf, *outbuf, *expectedbuf; int inlen = strlen(input); int actualInlen; const int outlen_multiplier = 4 + sizeof(widechar) * 2; int outlen = inlen * outlen_multiplier; int expectedlen = strlen(expected); int funcStatus = 0; formtype *typeformbuf = NULL; int *inputPos = NULL; int *outputPos = NULL; int cursorPos = 0; inbuf = malloc(sizeof(widechar) * inlen); outbuf = malloc(sizeof(widechar) * outlen); expectedbuf = malloc(sizeof(widechar) * expectedlen); if (in.typeform != NULL) { typeformbuf = malloc(outlen * sizeof(formtype)); memcpy(typeformbuf, in.typeform, inlen * sizeof(formtype)); } if (in.cursorPos >= 0) { cursorPos = in.cursorPos; } if (in.max_outlen >= 0) { outlen = in.max_outlen; } inlen = _lou_extParseChars(input, inbuf); if (!inlen) { fprintf(stderr, "Cannot parse input string.\n"); retval = 1; goto fail; } if (in.real_inlen > inlen) { fprintf(stderr, "expected realInputLength (%d) may not exceed total input length " "(%d)\n", in.real_inlen, inlen); return 1; } if (expected_inputPos) { inputPos = malloc(sizeof(int) * outlen); } if (expected_outputPos) { outputPos = malloc(sizeof(int) * inlen); } actualInlen = inlen; // Note that this loop is not strictly needed to make the current tests pass, but // in the general case it is needed because it is theoretically possible that we // provided a too short output buffer. for (int k = 1; k <= 3; k++) { if (direction == 1) { funcStatus = _lou_backTranslate(tableList, in.display_table, inbuf, &actualInlen, outbuf, &outlen, typeformbuf, NULL, outputPos, inputPos, &cursorPos, in.mode, NULL, NULL); } else { funcStatus = _lou_translate(tableList, in.display_table, inbuf, &actualInlen, outbuf, &outlen, typeformbuf, NULL, outputPos, inputPos, &cursorPos, in.mode, NULL, NULL); } if (!funcStatus) { fprintf(stderr, "Translation failed.\n"); retval = 1; goto fail; } if (in.max_outlen >= 0 || inlen == actualInlen) { break; } else if (k < 3) { // Hm, something is not quite right. Try again with a larger outbuf free(outbuf); outlen = inlen * outlen_multiplier * (k + 1); outbuf = malloc(sizeof(widechar) * outlen); if (expected_inputPos) { free(inputPos); inputPos = malloc(sizeof(int) * outlen); } fprintf(stderr, "Warning: For %s: returned inlen (%d) differs from passed inlen " "(%d) " "using outbuf of size %d. Trying again with bigger outbuf " "(%d).\n", input, actualInlen, inlen, inlen * outlen_multiplier * k, outlen); actualInlen = inlen; } } expectedlen = _lou_extParseChars(expected, expectedbuf); for (i = 0; i < outlen && i < expectedlen && expectedbuf[i] == outbuf[i]; i++) ; if (i < outlen || i < expectedlen) { retval = 1; if (in.diagnostics) { outbuf[outlen] = 0; fprintf(stderr, "Input: '%s'\n", input); /* Print the original typeform not the typeformbuf, as the * latter has been modified by the translation and contains some * information about outbuf */ if (in.typeform != NULL) print_typeform(in.typeform, inlen); if (in.cursorPos >= 0) fprintf(stderr, "Cursor: %d\n", in.cursorPos); fprintf(stderr, "Expected: '%s' (length %d)\n", expected, expectedlen); fprintf(stderr, "Received: '"); print_widechars(outbuf, outlen); fprintf(stderr, "' (length %d)\n", outlen); uint8_t *expected_utf8; uint8_t *out_utf8; size_t expected_utf8_len; size_t out_utf8_len; #ifdef WIDECHARS_ARE_UCS4 expected_utf8 = u32_to_u8(&expectedbuf[i], 1, NULL, &expected_utf8_len); out_utf8 = u32_to_u8(&outbuf[i], 1, NULL, &out_utf8_len); #else expected_utf8 = u16_to_u8(&expectedbuf[i], 1, NULL, &expected_utf8_len); out_utf8 = u16_to_u8(&outbuf[i], 1, NULL, &out_utf8_len); #endif if (i < outlen && i < expectedlen) { fprintf(stderr, "Diff: Expected '%.*s' but received '%.*s' in index %d\n", (int)expected_utf8_len, expected_utf8, (int)out_utf8_len, out_utf8, i); } else if (i < expectedlen) { fprintf(stderr, "Diff: Expected '%.*s' but received nothing in index " "%d\n", (int)expected_utf8_len, expected_utf8, i); } else { fprintf(stderr, "Diff: Expected nothing but received '%.*s' in index " "%d\n", (int)out_utf8_len, out_utf8, i); } free(expected_utf8); free(out_utf8); } } if (expected_inputPos) { int error_printed = 0; for (i = 0; i < outlen; i++) { if (expected_inputPos[i] != inputPos[i]) { retval = 1; if (in.diagnostics) { if (!error_printed) { // Print only once fprintf(stderr, "Input position failure:\n"); error_printed = 1; } fprintf(stderr, "Expected %d, received %d in index %d\n", expected_inputPos[i], inputPos[i], i); } } } } if (expected_outputPos) { int error_printed = 0; for (i = 0; i < inlen; i++) { if (expected_outputPos[i] != outputPos[i]) { retval = 1; if (in.diagnostics) { if (!error_printed) { // Print only once fprintf(stderr, "Output position failure:\n"); error_printed = 1; } fprintf(stderr, "Expected %d, received %d in index %d\n", expected_outputPos[i], outputPos[i], i); } } } } if ((in.expected_cursorPos >= 0) && (cursorPos != in.expected_cursorPos)) { retval = 1; if (in.diagnostics) { fprintf(stderr, "Cursor position failure:\n"); fprintf(stderr, "Initial:%d Expected:%d Actual:%d \n", in.cursorPos, in.expected_cursorPos, cursorPos); } } if (in.max_outlen < 0 && inlen != actualInlen) { retval = 1; if (in.diagnostics) { fprintf(stderr, "Unexpected error happened: input length is not the same before " "as " "after the translation:\n"); fprintf(stderr, "Before: %d After: %d \n", inlen, actualInlen); } } else if (actualInlen > inlen) { retval = 1; if (in.diagnostics) { fprintf(stderr, "Unexpected error happened: returned input length (%d) exceeds " "total input length (%d)\n", actualInlen, inlen); } } else if (in.real_inlen >= 0 && in.real_inlen != actualInlen) { retval = 1; if (in.diagnostics) { fprintf(stderr, "Real input length failure:\n"); fprintf(stderr, "Expected: %d, received: %d\n", in.real_inlen, actualInlen); } } fail: free(inbuf); free(outbuf); free(expectedbuf); free(typeformbuf); free(inputPos); free(outputPos); if (direction == 2) { const char *tmp = input; input = expected; expected = tmp; expected_inputPos = in.expected_outputPos; expected_outputPos = in.expected_inputPos; direction = 1; continue; } else { break; } } return retval; } /* Helper function to convert a typeform string of '0's, '1's, '2's etc. * to the required format, which is an array of 0s, 1s, 2s, etc. * For example, "0000011111000" is converted to {0,0,0,0,0,1,1,1,1,1,0,0,0} * The caller is responsible for freeing the returned array. */ formtype * convert_typeform(const char *typeform_string) { int len = strlen(typeform_string); formtype *typeform = malloc(len * sizeof(formtype)); int i; for (i = 0; i < len; i++) typeform[i] = typeform_string[i] - '0'; return typeform; } void update_typeform(const char *typeform_string, formtype *typeform, const typeforms kind) { int len = strlen(typeform_string); int i; for (i = 0; i < len; i++) if (typeform_string[i] != ' ') typeform[i] |= kind; } int check_cursor_pos(const char *tableList, const char *str, const int *expected_pos) { widechar *inbuf; widechar *outbuf; int *inpos, *outpos; int inlen = strlen(str); int outlen = inlen; int cursor_pos; int i, retval = 0; inbuf = malloc(sizeof(widechar) * inlen); outbuf = malloc(sizeof(widechar) * inlen); inpos = malloc(sizeof(int) * inlen); outpos = malloc(sizeof(int) * inlen); inlen = _lou_extParseChars(str, inbuf); for (i = 0; i < inlen; i++) { cursor_pos = i; if (!lou_translate(tableList, inbuf, &inlen, outbuf, &outlen, NULL, NULL, NULL, NULL, &cursor_pos, compbrlAtCursor)) { fprintf(stderr, "Translation failed.\n"); retval = 1; goto fail; } if (expected_pos[i] != cursor_pos) { if (!retval) // Print only once fprintf(stderr, "Cursorpos failure:\n"); fprintf(stderr, "string='%s' cursor=%d ('%c') expected=%d received=%d ('%c')\n", str, i, str[i], expected_pos[i], cursor_pos, (char)outbuf[cursor_pos]); retval = 1; } } fail: free(inbuf); free(outbuf); free(inpos); free(outpos); return retval; } /* Check if a string is hyphenated as expected, by passing the * expected hyphenation position array. * * @return 0 if the hyphenation is as expected and 1 otherwise. */ int check_hyphenation_pos(const char *tableList, const char *str, const char *expected) { widechar *inbuf; char *hyphens = NULL; int inlen = strlen(str); int retval = 0; inbuf = malloc(sizeof(widechar) * inlen); inlen = _lou_extParseChars(str, inbuf); if (!inlen) { fprintf(stderr, "Cannot parse input string.\n"); retval = 1; goto fail; } hyphens = calloc(inlen + 1, sizeof(char)); if (!lou_hyphenate(tableList, inbuf, inlen, hyphens, 0)) { fprintf(stderr, "Hyphenation failed.\n"); retval = 1; goto fail; } if (strcmp(expected, hyphens)) { fprintf(stderr, "Input: '%s'\n", str); fprintf(stderr, "Expected: '%s'\n", expected); fprintf(stderr, "Received: '%s'\n", hyphens); retval = 1; } fail: free(inbuf); free(hyphens); return retval; } /** Check if a string is hyphenated as expected. * * mode is '0' when input is text and '1' when input is braille * * @return 0 if the hyphenation is as expected and 1 otherwise. */ int check_hyphenation( const char *tableList, const char *str, const char *expected, int mode) { widechar *inbuf; widechar *hyphenatedbuf = NULL; uint8_t *hyphenated = NULL; char *hyphens = NULL; int inlen = strlen(str); size_t hyphenatedlen = inlen * 2; int retval = 0; inbuf = malloc(sizeof(widechar) * inlen); inlen = _lou_extParseChars(str, inbuf); if (!inlen) { fprintf(stderr, "Cannot parse input string.\n"); retval = 1; goto fail; } hyphens = calloc(inlen + 1, sizeof(char)); if (!lou_hyphenate(tableList, inbuf, inlen, hyphens, mode)) { fprintf(stderr, "Hyphenation failed.\n"); retval = 1; goto fail; } if (hyphens[0] != '0') { fprintf(stderr, "Unexpected output from lou_hyphenate.\n"); retval = 1; goto fail; } hyphenatedbuf = malloc(sizeof(widechar) * hyphenatedlen); int i = 0; int j = 0; hyphenatedbuf[i++] = inbuf[j++]; for (; j < inlen; j++) { if (hyphens[j] == '2') hyphenatedbuf[i++] = (widechar)'|'; else if (hyphens[j] != '0') hyphenatedbuf[i++] = (widechar)'-'; hyphenatedbuf[i++] = inbuf[j]; } #ifdef WIDECHARS_ARE_UCS4 hyphenated = u32_to_u8(hyphenatedbuf, i, NULL, &hyphenatedlen); #else hyphenated = u16_to_u8(hyphenatedbuf, i, NULL, &hyphenatedlen); #endif if (!hyphenated) { fprintf(stderr, "Unexpected error during UTF-8 encoding\n"); free(hyphenatedbuf); retval = 2; goto fail; } if (strlen(expected) != hyphenatedlen || strncmp(expected, (const char *)hyphenated, hyphenatedlen)) { fprintf(stderr, "Input: '%s'\n", str); fprintf(stderr, "Expected: '%s'\n", expected); fprintf(stderr, "Received: '%.*s'\n", (int)hyphenatedlen, hyphenated); retval = 1; } free(hyphenatedbuf); free(hyphenated); fail: free(inbuf); free(hyphens); return retval; }