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