test/harness/testcase.c
author ali <ali@juiblex.co.uk>
Fri Jan 27 21:40:35 2012 +0000 (2012-01-27)
changeset 7 721e468c10f3
parent 6 faab25d520dd
child 8 cf332d440466
permissions -rw-r--r--
Add support for non-ASCII testcases
     1 #include <stdlib.h>
     2 #include <stdio.h>
     3 #include <string.h>
     4 #include <unistd.h>
     5 #include <errno.h>
     6 #ifdef WIN32
     7 #include <io.h>
     8 #endif
     9 #include <fcntl.h>
    10 #include <bl/bl.h>
    11 #include "testcase.h"
    12 
    13 GQuark testcase_error_quark(void)
    14 {
    15     return g_quark_from_static_string("testcase-error-quark");
    16 }
    17 
    18 #if !HAVE_MKSTEMP
    19 /*
    20  * An insecure implementation of mkstemp(), for those platforms that
    21  * don't support it.
    22  */
    23 int mkstemp(char *template)
    24 {
    25     int fd;
    26     char *s;
    27     for(;;)
    28     {
    29 	s=g_strdup(template);
    30 	mktemp(s);
    31 	if (!*s)
    32 	{
    33 	    errno=EEXIST;
    34 	    g_free(s);
    35 	    return -1;
    36 	}
    37 	fd=open(s,O_RDWR|O_CREAT|O_EXCL,0600);
    38 	if (fd>0)
    39 	{
    40 	    strcpy(template,s);
    41 	    g_free(s);
    42 	    return fd;
    43 	}
    44 	else
    45 	    g_free(s);
    46     }
    47 }
    48 #endif	/* !HAVE_MKSTEMP */
    49 
    50 /*
    51  * As write(), but always convert NL to CR NL.
    52  */
    53 static size_t write_text(int fd,const char *buf,size_t count,GError **error)
    54 {
    55     size_t i;
    56     FILE *fp;
    57     fd=dup(fd);
    58     if (fd<0)
    59 	return -1;
    60 #ifdef WIN32
    61     if (_setmode(fd,_O_BINARY)<0)
    62     {
    63 	close(fd);
    64 	g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
    65 	  "Failed to set mode of bookloupe input file to binary: %s",
    66 	  g_strerror(errno));
    67 	return -1;
    68     }
    69 #endif
    70     fp=fdopen(fd,"wb");
    71     if (!fp)
    72     {
    73 	close(fd);
    74 	g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
    75 	  "Failed to open stream to bookloupe input file: %s",
    76 	  g_strerror(errno));
    77 	return -1;
    78     }
    79     for(i=0;i<count;i++)
    80     {
    81 	if (buf[i]=='\n')
    82 	    if (putc('\r',fp)==EOF)
    83 	    {
    84 		g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
    85 		  "Error writing bookloupe input file: %s",g_strerror(errno));
    86 		(void)fclose(fp);
    87 		return -1;
    88 	    }
    89 	if (putc(buf[i],fp)==EOF)
    90 	{
    91 	    g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
    92 	      "Error writing bookloupe input file: %s",g_strerror(errno));
    93 	    (void)fclose(fp);
    94 	    return -1;
    95 	}
    96     }
    97     if (fclose(fp))
    98     {
    99 	g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
   100 	  "Error writing bookloupe input file: %s",g_strerror(errno));
   101 	return -1;
   102     }
   103     return count;
   104 }
   105 
   106 /*
   107  * Return the length (in bytes) or any common prefix between s1 and s2.
   108  */
   109 size_t common_prefix_length(const char *s1,const char *s2)
   110 {
   111     size_t i;
   112     for(i=0;s1[i] && s2[i] && s1[i]==s2[i];i++)
   113 	;
   114     return i;
   115 }
   116 
   117 void print_unexpected(const char *unexpected,gsize differs_at)
   118 {
   119     int col;
   120     const char *endp,*bol;
   121     GString *string;
   122     endp=strchr(unexpected+differs_at,'\n');
   123     if (!endp)
   124 	endp=unexpected+strlen(unexpected);
   125     string=g_string_new_len(unexpected,endp-unexpected);
   126     bol=strrchr(string->str,'\n');
   127     if (bol)
   128 	bol++;
   129     else
   130 	bol=string->str;
   131     col=differs_at-(bol-string->str);
   132     fprintf(stderr,"%s\n%*s^\n",string->str,col,"");
   133     g_string_free(string,TRUE);
   134 }
   135 
   136 gboolean spawn_bootloupe(const char *encoding,const char *standard_input,
   137   char **standard_output,char **filename,GError **error)
   138 {
   139     gboolean r;
   140     int fd,exit_status;
   141     size_t n,pos,offset;
   142     FILE *fp;
   143     char input[]="TEST-XXXXXX";
   144     char *command[3];
   145     char *output,*s;
   146     GError *tmp_err=NULL;
   147     if (standard_input)
   148     {
   149 	if (encoding)
   150 	{
   151 	    s=g_convert(standard_input,-1,encoding,"UTF-8",NULL,&n,&tmp_err);
   152 	    if (!s)
   153 	    {
   154 		g_propagate_prefixed_error(error,tmp_err,
   155 		  "Conversion to %s failed: ",encoding);
   156 		return FALSE;
   157 	    }
   158 	}
   159 	else
   160 	{
   161 	    s=g_strdup(standard_input);
   162 	    n=strlen(s);
   163 	}
   164     }
   165     else
   166     {
   167 	n=0;
   168 	s=NULL;
   169     }
   170     fd=mkstemp(input);
   171     if (n && write_text(fd,s,n,error)!=n)
   172     {
   173 	g_free(s);
   174 	close(fd);
   175 	(void)remove(input);
   176 	return FALSE;
   177     }
   178     g_free(s);
   179     close(fd);
   180     command[0]=getenv("BOOKLOUPE");
   181     if (!command[0])
   182 	command[0]="." G_DIR_SEPARATOR_S "bookloupe";
   183     command[1]=input;
   184     command[2]=NULL;
   185     if (standard_output)
   186     {
   187 	r=spawn_sync(command,&s,&exit_status,error);
   188 	if (r)
   189 	{
   190 	    if (encoding)
   191 	    {
   192 		output=g_convert(s,-1,"UTF-8",encoding,NULL,NULL,&tmp_err);
   193 		g_free(s);
   194 		if (!output)
   195 		{
   196 		    g_propagate_prefixed_error(error,tmp_err,
   197 		      "Conversion from %s failed: ",encoding);
   198 		    r=FALSE;
   199 		}
   200 	    }
   201 	    else
   202 	    {
   203 		output=s;
   204 		if (!g_utf8_validate(s,-1,NULL))
   205 		{
   206 		    g_set_error_literal(error,TESTCASE_ERROR,
   207 		      TESTCASE_ERROR_FAILED,
   208 		      "bookloupe output is not valid UTF-8");
   209 		    r=FALSE;
   210 		}
   211 	    }
   212 	}
   213     }
   214     else
   215     {
   216 	r=spawn_sync(command,NULL,&exit_status,error);
   217 	output=NULL;
   218     }
   219     (void)remove(input);
   220     if (r && exit_status)
   221     {
   222 	g_set_error(error,TESTCASE_ERROR,TESTCASE_ERROR_FAILED,
   223 	  "bookloupe exited with code %d",exit_status);
   224 	r=FALSE;
   225     }
   226     if (r && filename)
   227 	*filename=g_strdup(input);
   228     if (r && standard_output)
   229 	*standard_output=output;
   230     return r;
   231 }
   232 
   233 /*
   234  * Run a testcase, returning FALSE on fail or error and
   235  * TRUE on pass or expected-fail.
   236  * Suitable message(s) will be printed in all cases.
   237  */
   238 gboolean testcase_run(Testcase *testcase)
   239 {
   240     gboolean r;
   241     size_t pos,offset;
   242     GString *header,*expected;
   243     char *output,*filename,*s;
   244     GError *error=NULL;
   245     if (testcase->expected)
   246 	r=spawn_bootloupe(testcase->encoding,testcase->input,&output,&filename,
   247 	  &error);
   248     else
   249     {
   250 	r=spawn_bootloupe(testcase->encoding,testcase->input,NULL,NULL,&error);
   251         output=filename=NULL;
   252     }
   253     if (!r)
   254     {
   255 	fprintf(stderr,"%s: FAIL\n",testcase->basename);
   256 	fprintf(stderr,"%s\n",error->message);
   257 	g_error_free(error);
   258 	return FALSE;
   259     }
   260     if (testcase->expected)
   261     {
   262 	header=g_string_new("\n\nFile: ");
   263 	g_string_append(header,filename);
   264 	g_string_append(header,"\n");
   265 	expected=g_string_new(testcase->expected);
   266 	if (!g_str_has_prefix(output,header->str))
   267 	{
   268 	    fprintf(stderr,"%s: FAIL\n",testcase->basename);
   269 	    offset=common_prefix_length(output,header->str);
   270 	    fprintf(stderr,"Unexpected header from bookloupe:\n");
   271 	    print_unexpected(output,offset);
   272 	    r=FALSE;
   273 	}
   274 	pos=header->len;
   275 	if (r)
   276 	{
   277 	    /* Skip the summary */
   278 	    s=strstr(output+pos,"\n\n");
   279 	    if (s)
   280 		pos=s-output+2;
   281 	    else
   282 	    {
   283 		fprintf(stderr,"%s: FAIL\n",testcase->basename);
   284 		offset=common_prefix_length(output,header->str);
   285 		fprintf(stderr,"Unterminated summary from bookloupe:\n%s\n",
   286 		  output+pos);
   287 		r=FALSE;
   288 	    }
   289 	}
   290 	if (r && strcmp(output+pos,expected->str))
   291 	{
   292 	    fprintf(stderr,"%s: FAIL\n",testcase->basename);
   293 	    offset=common_prefix_length(output+pos,expected->str);
   294 	    if (!offset && !output[pos+offset])
   295 		fprintf(stderr,"Unexpected zero warnings from bookloupe.\n");
   296 	    else
   297 	    {
   298 		fprintf(stderr,"Unexpected output from bookloupe:\n");
   299 		print_unexpected(output+pos,offset);
   300 	    }
   301 	    r=FALSE;
   302 	}
   303 	g_string_free(header,TRUE);
   304 	g_string_free(expected,TRUE);
   305     }
   306     g_free(output);
   307     g_free(filename);
   308     if (r)
   309 	fprintf(stderr,"%s: PASS\n",testcase->basename);
   310     return r;
   311 }
   312 
   313 /*
   314  * Free a testcase.
   315  */
   316 void testcase_free(Testcase *testcase)
   317 {
   318     g_free(testcase->basename);
   319     g_free(testcase->input);
   320     g_free(testcase->expected);
   321     g_free(testcase->encoding);
   322     g_free(testcase);
   323 }