Fix bug #6: BL treats a slanted apostrophe ? as a word separator, not as a contraction or possessive
6 #include <glib/gstdio.h>
9 #include "testcaseinput.h"
11 GQuark testcase_error_quark(void)
13 return g_quark_from_static_string("testcase-error-quark");
17 * Return the length (in bytes) of any common prefix between s1 and s2.
18 * The returned length will always represent an exact number of characters.
20 size_t common_prefix_length(const char *s1,const char *s2)
26 c1=g_utf8_get_char(s1);
27 c2=g_utf8_get_char(s2);
30 s1=g_utf8_next_char(s1);
31 s2=g_utf8_next_char(s2);
36 void print_unexpected(const char *unexpected,gsize differs_at)
40 const char *endp,*bol,*s;
42 endp=strchr(unexpected+differs_at,'\n');
44 endp=unexpected+strlen(unexpected);
45 string=g_string_new_len(unexpected,endp-unexpected);
46 bol=strrchr(string->str,'\n');
53 endp=string->str+differs_at;
57 s=g_utf8_next_char(s);
60 else if (g_unichar_iswide(c))
62 else if (!g_unichar_iszerowidth(c))
65 g_print("%s\n%*s^\n",string->str,col,"");
66 g_string_free(string,TRUE);
70 * Create all the input files needed by a testcase and, if required,
71 * a temporary directory in which to store them.
73 gboolean testcase_create_input_files(Testcase *testcase,GError **error)
76 if (testcase->flags&TESTCASE_TMP_DIR)
78 testcase->tmpdir=g_strdup("TEST-XXXXXX");
79 if (!g_mkdtemp(testcase->tmpdir))
81 g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
82 "Failed to create temporary directory: %s",g_strerror(errno));
83 g_free(testcase->tmpdir);
84 testcase->tmpdir=NULL;
88 for(link=testcase->inputs;link;link=link->next)
89 if (!testcase_input_create(testcase,link->data,error))
91 for(link2=testcase->inputs;link2!=link;link2=link2->next)
92 (void)testcase_input_remove(testcase,link2->data,NULL);
95 (void)g_rmdir(testcase->tmpdir);
96 g_free(testcase->tmpdir);
97 testcase->tmpdir=NULL;
105 * Remove all the input files used by a testcase and, if created,
106 * the temporary directory in which they are stored.
108 gboolean testcase_remove_input_files(Testcase *testcase,GError **error)
111 GError *tmp_err=NULL;
112 gboolean retval=TRUE;
113 for(link=testcase->inputs;link;link=link->next)
114 if (!testcase_input_remove(testcase,link->data,&tmp_err))
116 if (error && !*error)
117 g_propagate_error(error,tmp_err);
119 g_clear_error(&tmp_err);
122 if (testcase->tmpdir)
124 if (g_rmdir(testcase->tmpdir))
126 if (error && !*error)
127 g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
128 "Failed to remove temporary directory: %s",g_strerror(errno));
131 g_free(testcase->tmpdir);
132 testcase->tmpdir=NULL;
138 * Replace every occurance of an input file name in <str> with the
139 * filename which holds that input. For input files with fixed names,
140 * this is a noop. For input files which use the "XXXXXX" sequence
141 * to create a unique filename, the XXXXXX will be replaced with the
142 * 6 characters that were chosen to be unique.
144 char *testcase_resolve_input_files(Testcase *testcase,const char *str)
149 TestcaseInput *input;
150 GString *filename=g_string_new(str);
151 for(link=testcase->inputs;link;link=link->next)
154 if (!input->name_used)
156 g_warning("%s: Input file uninstantiated",input->name);
162 s=strstr(filename->str+offset,input->name);
166 g_string_overwrite(filename,pos,input->name_used);
167 offset=pos+strlen(input->name);
171 return g_string_free(filename,FALSE);
174 gboolean testcase_spawn_bookloupe(Testcase *testcase,char **standard_output,
181 GError *tmp_err=NULL;
182 if (testcase->options)
183 argv=g_new(char *,g_strv_length(testcase->options)+3);
185 argv=g_new(char *,3);
186 s=getenv("BOOKLOUPE");
189 argv[0]=path_to_absolute(s);
190 for(i=0;testcase->options && testcase->options[i];i++)
191 argv[i+1]=testcase_resolve_input_files(testcase,testcase->options[i]);
192 argv[i+1]=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
196 r=spawn_sync(testcase->tmpdir,argv,&s,&exit_status,error);
199 if (testcase->encoding)
201 output=g_convert(s,-1,"UTF-8",testcase->encoding,NULL,NULL,
206 g_propagate_prefixed_error(error,tmp_err,
207 "Conversion from %s failed: ",testcase->encoding);
214 if (!g_utf8_validate(s,-1,NULL))
216 g_set_error_literal(error,TESTCASE_ERROR,
217 TESTCASE_ERROR_FAILED,
218 "bookloupe output is not valid UTF-8");
226 r=spawn_sync(testcase->tmpdir,argv,NULL,&exit_status,error);
230 if (r && exit_status)
232 g_set_error(error,TESTCASE_ERROR,TESTCASE_ERROR_FAILED,
233 "bookloupe exited with code %d",exit_status);
236 if (r && standard_output)
237 *standard_output=output;
242 * Parse a warning of the form:
244 * <echoed line> (ignored)
245 * " Line " <number> [" column " <number>] " - " <text> "\n"
246 * If not specified, the column is returned as 0.
247 * Returns: the number of bytes parsed, or -1 on error.
249 static ssize_t testcase_parse_warning(Testcase *testcase,const char *output,
250 guint *line,guint *column,char **text)
255 if (output[offset]!='\n')
257 g_print("%s: FAIL\n",testcase->basename);
258 g_print("Unexpected output from bookloupe:\n");
259 print_unexpected(output,offset);
263 s=strchr(output+offset,'\n');
266 g_print("%s: FAIL\n",testcase->basename);
267 g_print("Missing new-line in output from bookloupe:\n");
268 print_unexpected(output,offset);
272 if (!g_str_has_prefix(output+offset," Line "))
274 g_print("%s: FAIL\n",testcase->basename);
275 g_print("Unexpected output from bookloupe:\n");
276 offset+=common_prefix_length(output+offset," Line ");
277 print_unexpected(output,offset);
281 tmp=g_ascii_strtoull(output+offset,&endp,10);
282 if (tmp<1 || tmp>G_MAXUINT || tmp==G_MAXUINT64)
284 g_print("%s: FAIL\n",testcase->basename);
285 g_print("Unexpected output from bookloupe:\n");
286 print_unexpected(output,offset);
291 if (g_str_has_prefix(output+offset," column "))
294 tmp=g_ascii_strtoull(output+offset,&endp,10);
295 if (tmp<1 || tmp>G_MAXUINT || tmp==G_MAXUINT64)
297 g_print("%s: FAIL\n",testcase->basename);
298 g_print("Unexpected output from bookloupe:\n");
299 print_unexpected(output,offset);
307 if (!g_str_has_prefix(output+offset," - "))
309 g_print("%s: FAIL\n",testcase->basename);
310 g_print("Unexpected output from bookloupe:\n");
311 offset+=common_prefix_length(output+offset," - ");
312 print_unexpected(output,offset);
316 s=strchr(output+offset,'\n');
319 g_print("%s: FAIL\n",testcase->basename);
320 g_print("Missing new-line in output from bookloupe:\n");
321 print_unexpected(output,offset);
324 *text=g_strndup(output+offset,s-(output+offset));
329 * Check the warnings produced by bookloupe against either the
330 * unstructured testcase->expected or the structured testcase->warnings
333 static gboolean testcase_check_warnings(Testcase *testcase,const char *output,
339 int i,count_false_positive,count_false_negative;
340 int total_false_positive,total_false_negative;
342 guint *counts,line,column;
344 TestcaseWarning *warning;
345 TestcaseLocation *location;
347 if (testcase->expected)
349 if (strcmp(output,testcase->expected))
351 g_print("%s: FAIL\n",testcase->basename);
352 offset=common_prefix_length(output,testcase->expected);
353 if (!offset && !output[offset])
354 g_print("Unexpected zero warnings from bookloupe.\n");
357 g_print("Unexpected output from bookloupe:\n");
358 print_unexpected(output,offset);
364 counts=g_new0(guint,g_slist_length(testcase->warnings));
365 for(offset=0;output[offset];)
367 off=testcase_parse_warning(testcase,output+offset,&line,&column,&text);
374 for(link=testcase->warnings,i=0;link;link=link->next,i++)
377 if (strcmp(warning->text,text))
379 for(link2=warning->locations;link2;link2=link2->next)
381 location=link2->data;
382 if (location->line!=line || location->column!=column)
392 g_print("%s: FAIL\n",testcase->basename);
393 g_print("Unexpected warning from bookloupe:\n");
395 g_print(" Line %u column %u - %s\n",line,column,text);
397 g_print(" Line %u - %s\n",line,text);
404 count_false_positive=total_false_positive=0;
405 count_false_negative=total_false_negative=0;
406 for(link=testcase->warnings,i=0;r && link;link=link->next,i++)
409 if (!counts[i] && warning->is_real && !warning->xfail)
411 location=warning->locations->data;
412 g_print("%s: FAIL\n",testcase->basename);
413 g_print("Missing warning from bookloupe:\n");
414 if (location->column)
415 g_print(" Line %u column %u - %s\n",location->line,
416 location->column,warning->text);
418 g_print(" Line %u - %s\n",location->line,warning->text);
422 else if (warning->xfail)
424 if (warning->is_real)
426 total_false_negative++;
428 count_false_negative++;
430 else if (!warning->is_real)
432 total_false_positive++;
434 count_false_positive++;
439 if (count_false_positive && count_false_negative)
440 *xfail=g_strdup_printf(
441 "with %d of %d false positives and %d of %d false negatives",
442 count_false_positive,total_false_positive,
443 count_false_negative,total_false_negative);
444 else if (count_false_positive)
445 *xfail=g_strdup_printf("with %d of %d false positives",
446 count_false_positive,total_false_positive);
447 else if (count_false_negative)
448 *xfail=g_strdup_printf("with %d of %d false negatives",
449 count_false_negative,total_false_negative);
454 * Run a testcase, returning FALSE on fail or error and
455 * TRUE on pass or expected-fail.
456 * Suitable message(s) will be printed in all cases.
458 gboolean testcase_run(Testcase *testcase)
463 char *output,*filename,*s,*xfail=NULL;
465 if (!testcase_create_input_files(testcase,&error))
467 g_print("%s: FAIL\n",testcase->basename);
468 g_print("%s\n",error->message);
472 if (testcase->expected || testcase->warnings)
473 r=testcase_spawn_bookloupe(testcase,&output,&error);
476 r=testcase_spawn_bookloupe(testcase,NULL,&error);
481 g_print("%s: FAIL\n",testcase->basename);
482 g_print("%s\n",error->message);
484 (void)testcase_remove_input_files(testcase,NULL);
487 filename=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
488 if (!testcase_remove_input_files(testcase,&error))
490 g_print("%s: FAIL\n",testcase->basename);
491 g_print("%s\n",error->message);
495 if (testcase->expected || testcase->warnings)
497 header=g_string_new("\n\nFile: ");
498 g_string_append(header,filename);
499 g_string_append(header,"\n");
500 if (!g_str_has_prefix(output,header->str))
502 g_print("%s: FAIL\n",testcase->basename);
503 g_print("Unexpected header from bookloupe:\n");
504 offset=common_prefix_length(output,header->str);
505 print_unexpected(output,offset);
511 /* Skip the summary */
512 s=strstr(output+pos,"\n\n");
517 g_print("%s: FAIL\n",testcase->basename);
518 g_print("Unterminated summary from bookloupe:\n%s\n",
523 g_string_free(header,TRUE);
524 r=testcase_check_warnings(testcase,output+pos,&xfail);
531 g_print("%s: PASS (%s)\n",testcase->basename,xfail);
533 g_print("%s: PASS\n",testcase->basename);
540 * Free a testcase warning.
542 void testcase_warning_free(TestcaseWarning *warning)
544 g_slist_foreach(warning->locations,(GFunc)g_free,NULL);
545 g_slist_free(warning->locations);
546 g_free(warning->text);
553 void testcase_free(Testcase *testcase)
555 g_free(testcase->basename);
556 g_slist_foreach(testcase->inputs,(GFunc)testcase_input_free,NULL);
557 g_slist_free(testcase->inputs);
558 g_free(testcase->expected);
559 g_slist_foreach(testcase->warnings,(GFunc)testcase_warning_free,NULL);
560 g_slist_free(testcase->warnings);
561 g_free(testcase->encoding);
562 g_strfreev(testcase->options);