ali@0: #include ali@0: #include ali@0: #include ali@0: #include ali@9: #include ali@9: #include ali@5: #include ali@0: #include "testcase.h" ali@9: #include "testcaseinput.h" ali@0: ali@7: GQuark testcase_error_quark(void) ali@7: { ali@7: return g_quark_from_static_string("testcase-error-quark"); ali@7: } ali@7: ali@0: /* ali@8: * Return the length (in bytes) of any common prefix between s1 and s2. ali@8: * The returned length will always represent an exact number of characters. ali@0: */ ali@0: size_t common_prefix_length(const char *s1,const char *s2) ali@0: { ali@8: gunichar c1,c2; ali@8: const char *s=s1; ali@8: while(*s1 && *s2) ali@8: { ali@8: c1=g_utf8_get_char(s1); ali@8: c2=g_utf8_get_char(s2); ali@8: if (c1!=c2) ali@8: break; ali@8: s1=g_utf8_next_char(s1); ali@8: s2=g_utf8_next_char(s2); ali@8: } ali@8: return s1-s; ali@0: } ali@0: ali@7: void print_unexpected(const char *unexpected,gsize differs_at) ali@7: { ali@7: int col; ali@8: gunichar c; ali@8: const char *endp,*bol,*s; ali@7: GString *string; ali@7: endp=strchr(unexpected+differs_at,'\n'); ali@7: if (!endp) ali@7: endp=unexpected+strlen(unexpected); ali@7: string=g_string_new_len(unexpected,endp-unexpected); ali@7: bol=strrchr(string->str,'\n'); ali@7: if (bol) ali@7: bol++; ali@7: else ali@7: bol=string->str; ali@8: col=0; ali@8: s=bol; ali@8: endp=string->str+differs_at; ali@8: while(sstr,col,""); ali@7: g_string_free(string,TRUE); ali@7: } ali@7: ali@8: /* ali@9: * Create all the input files needed by a testcase and, if required, ali@9: * a temporary directory in which to store them. ali@8: */ ali@9: gboolean testcase_create_input_files(Testcase *testcase,GError **error) ali@8: { ali@9: GSList *link,*link2; ali@9: if (testcase->flags&TESTCASE_TMP_DIR) ali@8: { ali@9: testcase->tmpdir=g_strdup("TEST-XXXXXX"); ali@9: if (!g_mkdtemp(testcase->tmpdir)) ali@9: { ali@9: g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno), ali@9: "Failed to create temporary directory: %s",g_strerror(errno)); ali@9: g_free(testcase->tmpdir); ali@9: testcase->tmpdir=NULL; ali@9: return FALSE; ali@9: } ali@8: } ali@9: for(link=testcase->inputs;link;link=link->next) ali@9: if (!testcase_input_create(testcase,link->data,error)) ali@9: { ali@9: for(link2=testcase->inputs;link2!=link;link2=link2->next) ali@9: (void)testcase_input_remove(testcase,link2->data,NULL); ali@9: if (testcase->tmpdir) ali@9: { ali@9: (void)g_rmdir(testcase->tmpdir); ali@9: g_free(testcase->tmpdir); ali@9: testcase->tmpdir=NULL; ali@9: } ali@9: return FALSE; ali@9: } ali@9: return TRUE; ali@8: } ali@8: ali@9: /* ali@9: * Remove all the input files used by a testcase and, if created, ali@9: * the temporary directory in which they are stored. ali@9: */ ali@9: gboolean testcase_remove_input_files(Testcase *testcase,GError **error) ali@9: { ali@9: GSList *link; ali@9: GError *tmp_err=NULL; ali@9: gboolean retval=TRUE; ali@9: for(link=testcase->inputs;link;link=link->next) ali@9: if (!testcase_input_remove(testcase,link->data,&tmp_err)) ali@9: { ali@9: if (error && !*error) ali@9: g_propagate_error(error,tmp_err); ali@9: else ali@9: g_clear_error(&tmp_err); ali@9: retval=FALSE; ali@9: } ali@9: if (testcase->tmpdir) ali@9: { ali@9: if (g_rmdir(testcase->tmpdir)) ali@9: { ali@9: if (error && !*error) ali@9: g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno), ali@9: "Failed to remove temporary directory: %s",g_strerror(errno)); ali@9: retval=FALSE; ali@9: } ali@9: g_free(testcase->tmpdir); ali@9: testcase->tmpdir=NULL; ali@9: } ali@9: return retval; ali@9: } ali@9: ali@9: /* ali@9: * Replace every occurance of an input file name in with the ali@9: * filename which holds that input. For input files with fixed names, ali@9: * this is a noop. For input files which use the "XXXXXX" sequence ali@9: * to create a unique filename, the XXXXXX will be replaced with the ali@9: * 6 characters that were chosen to be unique. ali@9: */ ali@9: char *testcase_resolve_input_files(Testcase *testcase,const char *str) ali@9: { ali@9: GSList *link; ali@9: gsize offset,pos; ali@9: char *s; ali@9: TestcaseInput *input; ali@9: GString *filename=g_string_new(str); ali@9: for(link=testcase->inputs;link;link=link->next) ali@9: { ali@9: input=link->data; ali@9: if (!input->name_used) ali@9: { ali@9: g_warning("%s: Input file uninstantiated",input->name); ali@9: continue; ali@9: } ali@9: offset=0; ali@9: do ali@9: { ali@9: s=strstr(filename->str+offset,input->name); ali@9: if (s) ali@9: { ali@9: pos=s-filename->str; ali@9: g_string_overwrite(filename,pos,input->name_used); ali@9: offset=pos+strlen(input->name); ali@9: } ali@9: } while(s); ali@9: } ali@9: return g_string_free(filename,FALSE); ali@9: } ali@9: ali@9: gboolean testcase_spawn_bookloupe(Testcase *testcase,char **standard_output, ali@9: GError **error) ali@7: { ali@7: gboolean r; ali@9: int i,exit_status; ali@9: char **argv; ali@9: char *output,*s; ali@7: GError *tmp_err=NULL; ali@9: if (testcase->options) ali@9: argv=g_new(char *,g_strv_length(testcase->options)+3); ali@7: else ali@9: argv=g_new(char *,3); ali@9: s=getenv("BOOKLOUPE"); ali@9: if (!s) ali@9: s="bookloupe"; ali@9: argv[0]=path_to_absolute(s); ali@9: for(i=0;testcase->options && testcase->options[i];i++) ali@9: argv[i+1]=testcase_resolve_input_files(testcase,testcase->options[i]); ali@9: argv[i+1]=testcase_resolve_input_files(testcase,"TEST-XXXXXX"); ali@9: argv[i+2]=NULL; ali@7: if (standard_output) ali@7: { ali@9: r=spawn_sync(testcase->tmpdir,argv,&s,&exit_status,error); ali@7: if (r) ali@7: { ali@9: if (testcase->encoding) ali@7: { ali@9: output=g_convert(s,-1,"UTF-8",testcase->encoding,NULL,NULL, ali@9: &tmp_err); ali@7: g_free(s); ali@7: if (!output) ali@7: { ali@7: g_propagate_prefixed_error(error,tmp_err, ali@9: "Conversion from %s failed: ",testcase->encoding); ali@7: r=FALSE; ali@7: } ali@7: } ali@7: else ali@7: { ali@7: output=s; ali@7: if (!g_utf8_validate(s,-1,NULL)) ali@7: { ali@7: g_set_error_literal(error,TESTCASE_ERROR, ali@7: TESTCASE_ERROR_FAILED, ali@7: "bookloupe output is not valid UTF-8"); ali@7: r=FALSE; ali@7: } ali@7: } ali@7: } ali@7: } ali@7: else ali@7: { ali@9: r=spawn_sync(testcase->tmpdir,argv,NULL,&exit_status,error); ali@7: output=NULL; ali@7: } ali@9: g_strfreev(argv); ali@7: if (r && exit_status) ali@7: { ali@7: g_set_error(error,TESTCASE_ERROR,TESTCASE_ERROR_FAILED, ali@7: "bookloupe exited with code %d",exit_status); ali@7: r=FALSE; ali@7: } ali@7: if (r && standard_output) ali@7: *standard_output=output; ali@7: return r; ali@7: } ali@7: ali@0: /* ali@0: * Run a testcase, returning FALSE on fail or error and ali@0: * TRUE on pass or expected-fail. ali@0: * Suitable message(s) will be printed in all cases. ali@0: */ ali@6: gboolean testcase_run(Testcase *testcase) ali@0: { ali@6: gboolean r; ali@7: size_t pos,offset; ali@7: GString *header,*expected; ali@7: char *output,*filename,*s; ali@7: GError *error=NULL; ali@9: if (!testcase_create_input_files(testcase,&error)) ali@9: { ali@9: fprintf(stderr,"%s: FAIL\n",testcase->basename); ali@9: fprintf(stderr,"%s\n",error->message); ali@9: g_error_free(error); ali@9: return FALSE; ali@9: } ali@7: if (testcase->expected) ali@9: r=testcase_spawn_bookloupe(testcase,&output,&error); ali@0: else ali@0: { ali@9: r=testcase_spawn_bookloupe(testcase,NULL,&error); ali@9: output=NULL; ali@7: } ali@7: if (!r) ali@7: { ali@7: fprintf(stderr,"%s: FAIL\n",testcase->basename); ali@7: fprintf(stderr,"%s\n",error->message); ali@7: g_error_free(error); ali@9: (void)testcase_remove_input_files(testcase,NULL); ali@9: return FALSE; ali@9: } ali@9: filename=testcase_resolve_input_files(testcase,"TEST-XXXXXX"); ali@9: if (!testcase_remove_input_files(testcase,&error)) ali@9: { ali@9: fprintf(stderr,"%s: FAIL\n",testcase->basename); ali@9: fprintf(stderr,"%s\n",error->message); ali@9: g_error_free(error); ali@0: return FALSE; ali@0: } ali@0: if (testcase->expected) ali@0: { ali@7: header=g_string_new("\n\nFile: "); ali@7: g_string_append(header,filename); ali@7: g_string_append(header,"\n"); ali@7: expected=g_string_new(testcase->expected); ali@7: if (!g_str_has_prefix(output,header->str)) ali@7: { ali@7: fprintf(stderr,"%s: FAIL\n",testcase->basename); ali@7: offset=common_prefix_length(output,header->str); ali@7: fprintf(stderr,"Unexpected header from bookloupe:\n"); ali@7: print_unexpected(output,offset); ali@7: r=FALSE; ali@7: } ali@7: pos=header->len; ali@7: if (r) ali@7: { ali@7: /* Skip the summary */ ali@7: s=strstr(output+pos,"\n\n"); ali@7: if (s) ali@7: pos=s-output+2; ali@7: else ali@7: { ali@7: fprintf(stderr,"%s: FAIL\n",testcase->basename); ali@7: offset=common_prefix_length(output,header->str); ali@7: fprintf(stderr,"Unterminated summary from bookloupe:\n%s\n", ali@7: output+pos); ali@7: r=FALSE; ali@7: } ali@7: } ali@7: if (r && strcmp(output+pos,expected->str)) ali@7: { ali@7: fprintf(stderr,"%s: FAIL\n",testcase->basename); ali@7: offset=common_prefix_length(output+pos,expected->str); ali@7: if (!offset && !output[pos+offset]) ali@7: fprintf(stderr,"Unexpected zero warnings from bookloupe.\n"); ali@7: else ali@7: { ali@7: fprintf(stderr,"Unexpected output from bookloupe:\n"); ali@7: print_unexpected(output+pos,offset); ali@7: } ali@7: r=FALSE; ali@7: } ali@7: g_string_free(header,TRUE); ali@7: g_string_free(expected,TRUE); ali@0: } ali@9: g_free(filename); ali@6: g_free(output); ali@7: if (r) ali@0: fprintf(stderr,"%s: PASS\n",testcase->basename); ali@7: return r; ali@0: } ali@0: ali@0: /* ali@0: * Free a testcase. ali@0: */ ali@0: void testcase_free(Testcase *testcase) ali@0: { ali@6: g_free(testcase->basename); ali@9: g_slist_foreach(testcase->inputs,(GFunc)testcase_input_free,NULL); ali@9: g_slist_free(testcase->inputs); ali@6: g_free(testcase->expected); ali@7: g_free(testcase->encoding); ali@9: g_strfreev(testcase->options); ali@6: g_free(testcase); ali@0: }