1 /* liblouis Braille Translation and Back-Translation Library
2 
3    Copyright (C) 2008 Eitan Isaacson <eitan@ascender.com>
4    Copyright (C) 2012 James Teh <jamie@nvaccess.org>
5    Copyright (C) 2012 Bert Frees <bertfrees@gmail.com>
6    Copyright (C) 2014 Mesar Hameed <mesar.hameed@gmail.com>
7    Copyright (C) 2015 Mike Gray <mgray@aph.org>
8    Copyright (C) 2010-2017 Swiss Library for the Blind, Visually Impaired and Print
9    Disabled
10    Copyright (C) 2016-2017 Davy Kager <mail@davykager.nl>
11 
12    Copying and distribution of this file, with or without modification,
13    are permitted in any medium without royalty provided the copyright
14    notice and this notice are preserved. This file is offered as-is,
15    without any warranty.
16 */
17 
18 /**
19  * @file
20  * @brief Test helper functions
21  */
22 
23 #include <config.h>
24 #include <assert.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include "liblouis.h"
29 #include "internal.h"
30 #include "brl_checks.h"
31 #include "unistr.h"
32 
33 static void
print_int_array(const char * prefix,int * pos_list,int len)34 print_int_array(const char *prefix, int *pos_list, int len) {
35 	int i;
36 	fprintf(stderr, "%s ", prefix);
37 	for (i = 0; i < len; i++) fprintf(stderr, "%d ", pos_list[i]);
38 	fprintf(stderr, "\n");
39 }
40 
41 static void
print_typeform(const formtype * typeform,int len)42 print_typeform(const formtype *typeform, int len) {
43 	int i;
44 	fprintf(stderr, "Typeform:  ");
45 	for (i = 0; i < len; i++) fprintf(stderr, "%hi", typeform[i]);
46 	fprintf(stderr, "\n");
47 }
48 
49 static void
print_widechars(widechar * buffer,int length)50 print_widechars(widechar *buffer, int length) {
51 	uint8_t *result_buf;
52 	size_t result_len;
53 
54 #ifdef WIDECHARS_ARE_UCS4
55 	result_buf = u32_to_u8(buffer, length, NULL, &result_len);
56 #else
57 	result_buf = u16_to_u8(buffer, length, NULL, &result_len);
58 #endif
59 	fprintf(stderr, "%.*s", (int)result_len, result_buf);
60 	free(result_buf);
61 }
62 
63 /* direction, 0=forward, 1=backwards, 2=both directions. If diagnostics is 1 then
64  * print diagnostics in case where the translation is not as
65  * expected */
66 
67 int
check_base(const char * tableList,const char * input,const char * expected,optional_test_params in)68 check_base(const char *tableList, const char *input, const char *expected,
69 		optional_test_params in) {
70 
71 	int i, retval = 0;
72 	int direction = in.direction;
73 	const int *expected_inputPos = in.expected_inputPos;
74 	const int *expected_outputPos = in.expected_outputPos;
75 	if (in.direction < 0 || in.direction > 2) {
76 		fprintf(stderr, "Invalid direction.\n");
77 		return 1;
78 	}
79 	if (in.direction != 0 && in.typeform != NULL) {
80 		// Currently, in backward translation, nothing is done with the initial value of
81 		// the typeform argument, and on return it always contains all zeros, so it
82 		// doesn't make any sense to use typeforms in backward translation tests.
83 		fprintf(stderr, "typeforms only supported with testmode 'forward'\n");
84 		return 1;
85 	}
86 	if (in.direction == 2 && in.cursorPos >= 0) {
87 		fprintf(stderr, "cursorPos not supported with testmode 'bothDirections'\n");
88 		return 1;
89 	}
90 	if (in.direction == 2 && in.max_outlen >= 0) {
91 		fprintf(stderr, "maxOutputLength not supported with testmode 'bothDirections'\n");
92 		return 1;
93 	}
94 	if (in.real_inlen >= 0 && in.max_outlen < 0) {
95 		fprintf(stderr,
96 				"realInputLength not supported when maxOutputLength is not specified\n");
97 		return 1;
98 	}
99 	while (1) {
100 		widechar *inbuf, *outbuf, *expectedbuf;
101 		int inlen = strlen(input);
102 		int actualInlen;
103 		const int outlen_multiplier = 4 + sizeof(widechar) * 2;
104 		int outlen = inlen * outlen_multiplier;
105 		int expectedlen = strlen(expected);
106 		int funcStatus = 0;
107 		formtype *typeformbuf = NULL;
108 		int *inputPos = NULL;
109 		int *outputPos = NULL;
110 		int cursorPos = 0;
111 		inbuf = malloc(sizeof(widechar) * inlen);
112 		outbuf = malloc(sizeof(widechar) * outlen);
113 		expectedbuf = malloc(sizeof(widechar) * expectedlen);
114 		if (in.typeform != NULL) {
115 			typeformbuf = malloc(outlen * sizeof(formtype));
116 			memcpy(typeformbuf, in.typeform, inlen * sizeof(formtype));
117 		}
118 		if (in.cursorPos >= 0) {
119 			cursorPos = in.cursorPos;
120 		}
121 		if (in.max_outlen >= 0) {
122 			outlen = in.max_outlen;
123 		}
124 		inlen = _lou_extParseChars(input, inbuf);
125 		if (!inlen) {
126 			fprintf(stderr, "Cannot parse input string.\n");
127 			retval = 1;
128 			goto fail;
129 		}
130 		if (in.real_inlen > inlen) {
131 			fprintf(stderr,
132 					"expected realInputLength (%d) may not exceed total input length "
133 					"(%d)\n",
134 					in.real_inlen, inlen);
135 			return 1;
136 		}
137 		if (expected_inputPos) {
138 			inputPos = malloc(sizeof(int) * outlen);
139 		}
140 		if (expected_outputPos) {
141 			outputPos = malloc(sizeof(int) * inlen);
142 		}
143 		actualInlen = inlen;
144 		// Note that this loop is not strictly needed to make the current tests pass, but
145 		// in the general case it is needed because it is theoretically possible that we
146 		// provided a too short output buffer.
147 		for (int k = 1; k <= 3; k++) {
148 			if (direction == 1) {
149 				funcStatus = _lou_backTranslate(tableList, in.display_table, inbuf,
150 						&actualInlen, outbuf, &outlen, typeformbuf, NULL, outputPos,
151 						inputPos, &cursorPos, in.mode, NULL, NULL);
152 			} else {
153 				funcStatus = _lou_translate(tableList, in.display_table, inbuf,
154 						&actualInlen, outbuf, &outlen, typeformbuf, NULL, outputPos,
155 						inputPos, &cursorPos, in.mode, NULL, NULL);
156 			}
157 			if (!funcStatus) {
158 				fprintf(stderr, "Translation failed.\n");
159 				retval = 1;
160 				goto fail;
161 			}
162 			if (in.max_outlen >= 0 || inlen == actualInlen) {
163 				break;
164 			} else if (k < 3) {
165 				// Hm, something is not quite right. Try again with a larger outbuf
166 				free(outbuf);
167 				outlen = inlen * outlen_multiplier * (k + 1);
168 				outbuf = malloc(sizeof(widechar) * outlen);
169 				if (expected_inputPos) {
170 					free(inputPos);
171 					inputPos = malloc(sizeof(int) * outlen);
172 				}
173 				fprintf(stderr,
174 						"Warning: For %s: returned inlen (%d) differs from passed inlen "
175 						"(%d) "
176 						"using outbuf of size %d. Trying again with bigger outbuf "
177 						"(%d).\n",
178 						input, actualInlen, inlen, inlen * outlen_multiplier * k, outlen);
179 				actualInlen = inlen;
180 			}
181 		}
182 		expectedlen = _lou_extParseChars(expected, expectedbuf);
183 		for (i = 0; i < outlen && i < expectedlen && expectedbuf[i] == outbuf[i]; i++)
184 			;
185 		if (i < outlen || i < expectedlen) {
186 			retval = 1;
187 			if (in.diagnostics) {
188 				outbuf[outlen] = 0;
189 				fprintf(stderr, "Input:    '%s'\n", input);
190 				/* Print the original typeform not the typeformbuf, as the
191 				 * latter has been modified by the translation and contains some
192 				 * information about outbuf */
193 				if (in.typeform != NULL) print_typeform(in.typeform, inlen);
194 				if (in.cursorPos >= 0) fprintf(stderr, "Cursor:   %d\n", in.cursorPos);
195 				fprintf(stderr, "Expected: '%s' (length %d)\n", expected, expectedlen);
196 				fprintf(stderr, "Received: '");
197 				print_widechars(outbuf, outlen);
198 				fprintf(stderr, "' (length %d)\n", outlen);
199 
200 				uint8_t *expected_utf8;
201 				uint8_t *out_utf8;
202 				size_t expected_utf8_len;
203 				size_t out_utf8_len;
204 #ifdef WIDECHARS_ARE_UCS4
205 				expected_utf8 = u32_to_u8(&expectedbuf[i], 1, NULL, &expected_utf8_len);
206 				out_utf8 = u32_to_u8(&outbuf[i], 1, NULL, &out_utf8_len);
207 #else
208 				expected_utf8 = u16_to_u8(&expectedbuf[i], 1, NULL, &expected_utf8_len);
209 				out_utf8 = u16_to_u8(&outbuf[i], 1, NULL, &out_utf8_len);
210 #endif
211 
212 				if (i < outlen && i < expectedlen) {
213 					fprintf(stderr,
214 							"Diff:     Expected '%.*s' but received '%.*s' in index %d\n",
215 							(int)expected_utf8_len, expected_utf8, (int)out_utf8_len,
216 							out_utf8, i);
217 				} else if (i < expectedlen) {
218 					fprintf(stderr,
219 							"Diff:     Expected '%.*s' but received nothing in index "
220 							"%d\n",
221 							(int)expected_utf8_len, expected_utf8, i);
222 				} else {
223 					fprintf(stderr,
224 							"Diff:     Expected nothing but received '%.*s' in index "
225 							"%d\n",
226 							(int)out_utf8_len, out_utf8, i);
227 				}
228 				free(expected_utf8);
229 				free(out_utf8);
230 			}
231 		}
232 		if (expected_inputPos) {
233 			int error_printed = 0;
234 			for (i = 0; i < outlen; i++) {
235 				if (expected_inputPos[i] != inputPos[i]) {
236 					retval = 1;
237 					if (in.diagnostics) {
238 						if (!error_printed) {  // Print only once
239 							fprintf(stderr, "Input position failure:\n");
240 							error_printed = 1;
241 						}
242 						fprintf(stderr, "Expected %d, received %d in index %d\n",
243 								expected_inputPos[i], inputPos[i], i);
244 					}
245 				}
246 			}
247 		}
248 		if (expected_outputPos) {
249 			int error_printed = 0;
250 			for (i = 0; i < inlen; i++) {
251 				if (expected_outputPos[i] != outputPos[i]) {
252 					retval = 1;
253 					if (in.diagnostics) {
254 						if (!error_printed) {  // Print only once
255 							fprintf(stderr, "Output position failure:\n");
256 							error_printed = 1;
257 						}
258 						fprintf(stderr, "Expected %d, received %d in index %d\n",
259 								expected_outputPos[i], outputPos[i], i);
260 					}
261 				}
262 			}
263 		}
264 		if ((in.expected_cursorPos >= 0) && (cursorPos != in.expected_cursorPos)) {
265 			retval = 1;
266 			if (in.diagnostics) {
267 				fprintf(stderr, "Cursor position failure:\n");
268 				fprintf(stderr, "Initial:%d Expected:%d Actual:%d \n", in.cursorPos,
269 						in.expected_cursorPos, cursorPos);
270 			}
271 		}
272 		if (in.max_outlen < 0 && inlen != actualInlen) {
273 			retval = 1;
274 			if (in.diagnostics) {
275 				fprintf(stderr,
276 						"Unexpected error happened: input length is not the same before "
277 						"as "
278 						"after the translation:\n");
279 				fprintf(stderr, "Before: %d After: %d \n", inlen, actualInlen);
280 			}
281 		} else if (actualInlen > inlen) {
282 			retval = 1;
283 			if (in.diagnostics) {
284 				fprintf(stderr,
285 						"Unexpected error happened: returned input length (%d) exceeds "
286 						"total input length (%d)\n",
287 						actualInlen, inlen);
288 			}
289 		} else if (in.real_inlen >= 0 && in.real_inlen != actualInlen) {
290 			retval = 1;
291 			if (in.diagnostics) {
292 				fprintf(stderr, "Real input length failure:\n");
293 				fprintf(stderr, "Expected: %d, received: %d\n", in.real_inlen,
294 						actualInlen);
295 			}
296 		}
297 
298 	fail:
299 		free(inbuf);
300 		free(outbuf);
301 		free(expectedbuf);
302 		free(typeformbuf);
303 		free(inputPos);
304 		free(outputPos);
305 
306 		if (direction == 2) {
307 			const char *tmp = input;
308 			input = expected;
309 			expected = tmp;
310 			expected_inputPos = in.expected_outputPos;
311 			expected_outputPos = in.expected_inputPos;
312 			direction = 1;
313 			continue;
314 		} else {
315 			break;
316 		}
317 	}
318 
319 	return retval;
320 }
321 
322 /* Helper function to convert a typeform string of '0's, '1's, '2's etc.
323  * to the required format, which is an array of 0s, 1s, 2s, etc.
324  * For example, "0000011111000" is converted to {0,0,0,0,0,1,1,1,1,1,0,0,0}
325  * The caller is responsible for freeing the returned array. */
326 formtype *
convert_typeform(const char * typeform_string)327 convert_typeform(const char *typeform_string) {
328 	int len = strlen(typeform_string);
329 	formtype *typeform = malloc(len * sizeof(formtype));
330 	int i;
331 	for (i = 0; i < len; i++) typeform[i] = typeform_string[i] - '0';
332 	return typeform;
333 }
334 
335 void
update_typeform(const char * typeform_string,formtype * typeform,const typeforms kind)336 update_typeform(const char *typeform_string, formtype *typeform, const typeforms kind) {
337 	int len = strlen(typeform_string);
338 	int i;
339 	for (i = 0; i < len; i++)
340 		if (typeform_string[i] != ' ') typeform[i] |= kind;
341 }
342 
343 int
check_cursor_pos(const char * tableList,const char * str,const int * expected_pos)344 check_cursor_pos(const char *tableList, const char *str, const int *expected_pos) {
345 	widechar *inbuf;
346 	widechar *outbuf;
347 	int *inpos, *outpos;
348 	int inlen = strlen(str);
349 	int outlen = inlen;
350 	int cursor_pos;
351 	int i, retval = 0;
352 
353 	inbuf = malloc(sizeof(widechar) * inlen);
354 	outbuf = malloc(sizeof(widechar) * inlen);
355 	inpos = malloc(sizeof(int) * inlen);
356 	outpos = malloc(sizeof(int) * inlen);
357 	inlen = _lou_extParseChars(str, inbuf);
358 
359 	for (i = 0; i < inlen; i++) {
360 		cursor_pos = i;
361 		if (!lou_translate(tableList, inbuf, &inlen, outbuf, &outlen, NULL, NULL, NULL,
362 					NULL, &cursor_pos, compbrlAtCursor)) {
363 			fprintf(stderr, "Translation failed.\n");
364 			retval = 1;
365 			goto fail;
366 		}
367 		if (expected_pos[i] != cursor_pos) {
368 			if (!retval)  // Print only once
369 				fprintf(stderr, "Cursorpos failure:\n");
370 			fprintf(stderr,
371 					"string='%s' cursor=%d ('%c') expected=%d received=%d ('%c')\n", str,
372 					i, str[i], expected_pos[i], cursor_pos, (char)outbuf[cursor_pos]);
373 			retval = 1;
374 		}
375 	}
376 
377 fail:
378 	free(inbuf);
379 	free(outbuf);
380 	free(inpos);
381 	free(outpos);
382 	return retval;
383 }
384 
385 /* Check if a string is hyphenated as expected, by passing the
386  * expected hyphenation position array.
387  *
388  * @return 0 if the hyphenation is as expected and 1 otherwise.
389  */
390 int
check_hyphenation_pos(const char * tableList,const char * str,const char * expected)391 check_hyphenation_pos(const char *tableList, const char *str, const char *expected) {
392 	widechar *inbuf;
393 	char *hyphens = NULL;
394 	int inlen = strlen(str);
395 	int retval = 0;
396 
397 	inbuf = malloc(sizeof(widechar) * inlen);
398 	inlen = _lou_extParseChars(str, inbuf);
399 	if (!inlen) {
400 		fprintf(stderr, "Cannot parse input string.\n");
401 		retval = 1;
402 		goto fail;
403 	}
404 	hyphens = calloc(inlen + 1, sizeof(char));
405 
406 	if (!lou_hyphenate(tableList, inbuf, inlen, hyphens, 0)) {
407 		fprintf(stderr, "Hyphenation failed.\n");
408 		retval = 1;
409 		goto fail;
410 	}
411 
412 	if (strcmp(expected, hyphens)) {
413 		fprintf(stderr, "Input:    '%s'\n", str);
414 		fprintf(stderr, "Expected: '%s'\n", expected);
415 		fprintf(stderr, "Received: '%s'\n", hyphens);
416 		retval = 1;
417 	}
418 
419 fail:
420 	free(inbuf);
421 	free(hyphens);
422 	return retval;
423 }
424 
425 /** Check if a string is hyphenated as expected.
426  *
427  * mode is '0' when input is text and '1' when input is braille
428  *
429  * @return 0 if the hyphenation is as expected and 1 otherwise.
430  */
431 int
check_hyphenation(const char * tableList,const char * str,const char * expected,int mode)432 check_hyphenation(
433 		const char *tableList, const char *str, const char *expected, int mode) {
434 	widechar *inbuf;
435 	widechar *hyphenatedbuf = NULL;
436 	uint8_t *hyphenated = NULL;
437 	char *hyphens = NULL;
438 	int inlen = strlen(str);
439 	size_t hyphenatedlen = inlen * 2;
440 	int retval = 0;
441 
442 	inbuf = malloc(sizeof(widechar) * inlen);
443 	inlen = _lou_extParseChars(str, inbuf);
444 	if (!inlen) {
445 		fprintf(stderr, "Cannot parse input string.\n");
446 		retval = 1;
447 		goto fail;
448 	}
449 	hyphens = calloc(inlen + 1, sizeof(char));
450 
451 	if (!lou_hyphenate(tableList, inbuf, inlen, hyphens, mode)) {
452 		fprintf(stderr, "Hyphenation failed.\n");
453 		retval = 1;
454 		goto fail;
455 	}
456 	if (hyphens[0] != '0') {
457 		fprintf(stderr, "Unexpected output from lou_hyphenate.\n");
458 		retval = 1;
459 		goto fail;
460 	}
461 
462 	hyphenatedbuf = malloc(sizeof(widechar) * hyphenatedlen);
463 	int i = 0;
464 	int j = 0;
465 	hyphenatedbuf[i++] = inbuf[j++];
466 	for (; j < inlen; j++) {
467 		if (hyphens[j] == '2')
468 			hyphenatedbuf[i++] = (widechar)'|';
469 		else if (hyphens[j] != '0')
470 			hyphenatedbuf[i++] = (widechar)'-';
471 		hyphenatedbuf[i++] = inbuf[j];
472 	}
473 
474 #ifdef WIDECHARS_ARE_UCS4
475 	hyphenated = u32_to_u8(hyphenatedbuf, i, NULL, &hyphenatedlen);
476 #else
477 	hyphenated = u16_to_u8(hyphenatedbuf, i, NULL, &hyphenatedlen);
478 #endif
479 
480 	if (!hyphenated) {
481 		fprintf(stderr, "Unexpected error during UTF-8 encoding\n");
482 		free(hyphenatedbuf);
483 		retval = 2;
484 		goto fail;
485 	}
486 
487 	if (strlen(expected) != hyphenatedlen ||
488 			strncmp(expected, (const char *)hyphenated, hyphenatedlen)) {
489 		fprintf(stderr, "Input:    '%s'\n", str);
490 		fprintf(stderr, "Expected: '%s'\n", expected);
491 		fprintf(stderr, "Received: '%.*s'\n", (int)hyphenatedlen, hyphenated);
492 		retval = 1;
493 	}
494 
495 	free(hyphenatedbuf);
496 	free(hyphenated);
497 
498 fail:
499 	free(inbuf);
500 	free(hyphens);
501 	return retval;
502 }
503