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 summary produced by bookloupe against testcase->summary.
331 static gboolean testcase_check_summary(Testcase *testcase,const char *summary)
337 if (!testcase->summary.texts)
339 texts=g_slist_copy(testcase->summary.texts);
340 lines=g_strsplit(summary,"\n",0);
341 for(i=0;lines[i];i++)
343 if (!g_str_has_prefix(lines[i]," --> "))
345 for(lnk=texts;lnk;lnk=lnk->next)
346 if (!strcmp(lines[i]+7,lnk->data))
348 texts=g_slist_delete_link(texts,lnk);
356 g_print("%s: FAIL\n",testcase->basename);
357 g_print("Missing summary text from bookloupe:\n");
358 g_print(" --> %s\n",texts->data);
365 * Check the warnings produced by bookloupe against either the
366 * unstructured testcase->expected or the structured testcase->warnings
369 static gboolean testcase_check_warnings(Testcase *testcase,const char *output,
375 int i,count_false_positive,count_false_negative;
376 int total_false_positive,total_false_negative;
378 guint *counts,line,column;
380 TestcaseWarning *warning;
381 TestcaseLocation *location;
383 if (testcase->expected)
385 if (strcmp(output,testcase->expected))
387 g_print("%s: FAIL\n",testcase->basename);
388 offset=common_prefix_length(output,testcase->expected);
389 if (!offset && !output[offset])
390 g_print("Unexpected zero warnings from bookloupe.\n");
393 g_print("Unexpected output from bookloupe:\n");
394 print_unexpected(output,offset);
400 counts=g_new0(guint,g_slist_length(testcase->warnings));
401 for(offset=0;output[offset];)
403 off=testcase_parse_warning(testcase,output+offset,&line,&column,&text);
410 for(link=testcase->warnings,i=0;link;link=link->next,i++)
413 if (strcmp(warning->text,text))
415 for(link2=warning->locations;link2;link2=link2->next)
417 location=link2->data;
418 if (location->line!=line || location->column!=column)
428 g_print("%s: FAIL\n",testcase->basename);
429 g_print("Unexpected warning from bookloupe:\n");
431 g_print(" Line %u column %u - %s\n",line,column,text);
433 g_print(" Line %u - %s\n",line,text);
440 count_false_positive=total_false_positive=0;
441 count_false_negative=total_false_negative=0;
442 for(link=testcase->warnings,i=0;r && link;link=link->next,i++)
445 if (!counts[i] && warning->is_real && !warning->xfail)
447 location=warning->locations->data;
448 g_print("%s: FAIL\n",testcase->basename);
449 g_print("Missing warning from bookloupe:\n");
450 if (location->column)
451 g_print(" Line %u column %u - %s\n",location->line,
452 location->column,warning->text);
454 g_print(" Line %u - %s\n",location->line,warning->text);
458 else if (warning->xfail)
460 if (warning->is_real)
462 total_false_negative++;
464 count_false_negative++;
466 else if (!warning->is_real)
468 total_false_positive++;
470 count_false_positive++;
475 if (count_false_positive && count_false_negative)
476 *xfail=g_strdup_printf(
477 "with %d of %d false positives and %d of %d false negatives",
478 count_false_positive,total_false_positive,
479 count_false_negative,total_false_negative);
480 else if (count_false_positive)
481 *xfail=g_strdup_printf("with %d of %d false positives",
482 count_false_positive,total_false_positive);
483 else if (count_false_negative)
484 *xfail=g_strdup_printf("with %d of %d false negatives",
485 count_false_negative,total_false_negative);
490 * Run a testcase, returning FALSE on fail or error and
491 * TRUE on pass or expected-fail.
492 * Suitable message(s) will be printed in all cases.
494 gboolean testcase_run(Testcase *testcase)
499 char *output,*filename,*s,*summary,*xfail=NULL;
501 if (!testcase_create_input_files(testcase,&error))
503 g_print("%s: FAIL\n",testcase->basename);
504 g_print("%s\n",error->message);
508 r=testcase_spawn_bookloupe(testcase,&output,&error);
511 g_print("%s: FAIL\n",testcase->basename);
512 g_print("%s\n",error->message);
514 (void)testcase_remove_input_files(testcase,NULL);
517 filename=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
518 if (!testcase_remove_input_files(testcase,&error))
520 g_print("%s: FAIL\n",testcase->basename);
521 g_print("%s\n",error->message);
525 header=g_string_new("\n\nFile: ");
526 g_string_append(header,filename);
527 g_string_append(header,"\n");
528 if (!g_str_has_prefix(output,header->str))
530 g_print("%s: FAIL\n",testcase->basename);
531 g_print("Unexpected header from bookloupe:\n");
532 offset=common_prefix_length(output,header->str);
533 print_unexpected(output,offset);
539 /* Find the end of the summary */
540 s=strstr(output+pos,"\n\n");
543 summary=g_strndup(output+pos,s-(output+pos));
544 r=testcase_check_summary(testcase,summary);
550 g_print("%s: FAIL\n",testcase->basename);
551 g_print("Unterminated summary from bookloupe:\n%s\n",output+pos);
555 g_string_free(header,TRUE);
557 r=testcase_check_warnings(testcase,output+pos,&xfail);
563 g_print("%s: PASS (%s)\n",testcase->basename,xfail);
565 g_print("%s: PASS\n",testcase->basename);
572 * Run a testcase, returning FALSE on error.
573 * Bookloupe's output or a suitable error message will be shown.
575 gboolean testcase_show_output(Testcase *testcase)
580 r=testcase_create_input_files(testcase,&error);
583 r&=testcase_spawn_bookloupe(testcase,&output,&error);
584 r&=testcase_remove_input_files(testcase,&error);
587 g_print("%s",output);
590 g_print("%s\n",error->message);
597 * Free a testcase warning.
599 void testcase_warning_free(TestcaseWarning *warning)
601 g_slist_foreach(warning->locations,(GFunc)g_free,NULL);
602 g_slist_free(warning->locations);
603 g_free(warning->text);
610 void testcase_free(Testcase *testcase)
612 g_free(testcase->basename);
613 g_slist_foreach(testcase->inputs,(GFunc)testcase_input_free,NULL);
614 g_slist_free(testcase->inputs);
615 g_free(testcase->expected);
616 g_slist_foreach(testcase->warnings,(GFunc)testcase_warning_free,NULL);
617 g_slist_free(testcase->warnings);
618 g_free(testcase->encoding);
619 g_strfreev(testcase->options);