test/harness/testcase.c
author ali <ali@juiblex.co.uk>
Mon Jan 30 23:32:47 2012 +0000 (2012-01-30)
changeset 11 4a80c6053a66
parent 9 6a13fe0fc19e
child 17 2c88fd553e5d
permissions -rw-r--r--
Use Unicode output on MS-Windows consoles
ali@0
     1
#include <stdlib.h>
ali@0
     2
#include <stdio.h>
ali@0
     3
#include <string.h>
ali@0
     4
#include <errno.h>
ali@9
     5
#include <glib.h>
ali@9
     6
#include <glib/gstdio.h>
ali@5
     7
#include <bl/bl.h>
ali@0
     8
#include "testcase.h"
ali@9
     9
#include "testcaseinput.h"
ali@0
    10
ali@7
    11
GQuark testcase_error_quark(void)
ali@7
    12
{
ali@7
    13
    return g_quark_from_static_string("testcase-error-quark");
ali@7
    14
}
ali@7
    15
ali@0
    16
/*
ali@8
    17
 * Return the length (in bytes) of any common prefix between s1 and s2.
ali@8
    18
 * The returned length will always represent an exact number of characters.
ali@0
    19
 */
ali@0
    20
size_t common_prefix_length(const char *s1,const char *s2)
ali@0
    21
{
ali@8
    22
    gunichar c1,c2;
ali@8
    23
    const char *s=s1;
ali@8
    24
    while(*s1 && *s2)
ali@8
    25
    {
ali@8
    26
	c1=g_utf8_get_char(s1);
ali@8
    27
	c2=g_utf8_get_char(s2);
ali@8
    28
	if (c1!=c2)
ali@8
    29
	    break;
ali@8
    30
	s1=g_utf8_next_char(s1);
ali@8
    31
	s2=g_utf8_next_char(s2);
ali@8
    32
    }
ali@8
    33
    return s1-s;
ali@0
    34
}
ali@0
    35
ali@7
    36
void print_unexpected(const char *unexpected,gsize differs_at)
ali@7
    37
{
ali@7
    38
    int col;
ali@8
    39
    gunichar c;
ali@8
    40
    const char *endp,*bol,*s;
ali@7
    41
    GString *string;
ali@7
    42
    endp=strchr(unexpected+differs_at,'\n');
ali@7
    43
    if (!endp)
ali@7
    44
	endp=unexpected+strlen(unexpected);
ali@7
    45
    string=g_string_new_len(unexpected,endp-unexpected);
ali@7
    46
    bol=strrchr(string->str,'\n');
ali@7
    47
    if (bol)
ali@7
    48
	bol++;
ali@7
    49
    else
ali@7
    50
	bol=string->str;
ali@8
    51
    col=0;
ali@8
    52
    s=bol;
ali@8
    53
    endp=string->str+differs_at;
ali@8
    54
    while(s<endp)
ali@8
    55
    {
ali@8
    56
	c=g_utf8_get_char(s);
ali@8
    57
	s=g_utf8_next_char(s);
ali@8
    58
	if (c=='\t')
ali@8
    59
	    col=(col&~7)+8;
ali@8
    60
	else if (g_unichar_iswide(c))
ali@8
    61
	    col+=2;
ali@8
    62
	else if (!g_unichar_iszerowidth(c))
ali@8
    63
	    col++;
ali@8
    64
    }
ali@11
    65
    g_print("%s\n%*s^\n",string->str,col,"");
ali@7
    66
    g_string_free(string,TRUE);
ali@7
    67
}
ali@7
    68
ali@8
    69
/*
ali@9
    70
 * Create all the input files needed by a testcase and, if required,
ali@9
    71
 * a temporary directory in which to store them.
ali@8
    72
 */
ali@9
    73
gboolean testcase_create_input_files(Testcase *testcase,GError **error)
ali@8
    74
{
ali@9
    75
    GSList *link,*link2;
ali@9
    76
    if (testcase->flags&TESTCASE_TMP_DIR)
ali@8
    77
    {
ali@9
    78
	testcase->tmpdir=g_strdup("TEST-XXXXXX");
ali@9
    79
	if (!g_mkdtemp(testcase->tmpdir))
ali@9
    80
	{
ali@9
    81
	    g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
ali@9
    82
	      "Failed to create temporary directory: %s",g_strerror(errno));
ali@9
    83
	    g_free(testcase->tmpdir);
ali@9
    84
	    testcase->tmpdir=NULL;
ali@9
    85
	    return FALSE;
ali@9
    86
	}
ali@8
    87
    }
ali@9
    88
    for(link=testcase->inputs;link;link=link->next)
ali@9
    89
	if (!testcase_input_create(testcase,link->data,error))
ali@9
    90
	{
ali@9
    91
	    for(link2=testcase->inputs;link2!=link;link2=link2->next)
ali@9
    92
		(void)testcase_input_remove(testcase,link2->data,NULL);
ali@9
    93
	    if (testcase->tmpdir)
ali@9
    94
	    {
ali@9
    95
		(void)g_rmdir(testcase->tmpdir);
ali@9
    96
		g_free(testcase->tmpdir);
ali@9
    97
		testcase->tmpdir=NULL;
ali@9
    98
	    }
ali@9
    99
	    return FALSE;
ali@9
   100
	}
ali@9
   101
    return TRUE;
ali@8
   102
}
ali@8
   103
ali@9
   104
/*
ali@9
   105
 * Remove all the input files used by a testcase and, if created,
ali@9
   106
 * the temporary directory in which they are stored.
ali@9
   107
 */
ali@9
   108
gboolean testcase_remove_input_files(Testcase *testcase,GError **error)
ali@9
   109
{
ali@9
   110
    GSList *link;
ali@9
   111
    GError *tmp_err=NULL;
ali@9
   112
    gboolean retval=TRUE;
ali@9
   113
    for(link=testcase->inputs;link;link=link->next)
ali@9
   114
	if (!testcase_input_remove(testcase,link->data,&tmp_err))
ali@9
   115
	{
ali@9
   116
	    if (error && !*error)
ali@9
   117
		g_propagate_error(error,tmp_err);
ali@9
   118
	    else
ali@9
   119
		g_clear_error(&tmp_err);
ali@9
   120
	    retval=FALSE;
ali@9
   121
	}
ali@9
   122
    if (testcase->tmpdir)
ali@9
   123
    {
ali@9
   124
	if (g_rmdir(testcase->tmpdir))
ali@9
   125
	{
ali@9
   126
	    if (error && !*error)
ali@9
   127
		g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
ali@9
   128
		  "Failed to remove temporary directory: %s",g_strerror(errno));
ali@9
   129
	    retval=FALSE;
ali@9
   130
	}
ali@9
   131
	g_free(testcase->tmpdir);
ali@9
   132
	testcase->tmpdir=NULL;
ali@9
   133
    }
ali@9
   134
    return retval;
ali@9
   135
}
ali@9
   136
ali@9
   137
/*
ali@9
   138
 * Replace every occurance of an input file name in <str> with the
ali@9
   139
 * filename which holds that input. For input files with fixed names,
ali@9
   140
 * this is a noop. For input files which use the "XXXXXX" sequence
ali@9
   141
 * to create a unique filename, the XXXXXX will be replaced with the
ali@9
   142
 * 6 characters that were chosen to be unique.
ali@9
   143
 */
ali@9
   144
char *testcase_resolve_input_files(Testcase *testcase,const char *str)
ali@9
   145
{
ali@9
   146
    GSList *link;
ali@9
   147
    gsize offset,pos;
ali@9
   148
    char *s;
ali@9
   149
    TestcaseInput *input;
ali@9
   150
    GString *filename=g_string_new(str);
ali@9
   151
    for(link=testcase->inputs;link;link=link->next)
ali@9
   152
    {
ali@9
   153
	input=link->data;
ali@9
   154
	if (!input->name_used)
ali@9
   155
	{
ali@9
   156
	    g_warning("%s: Input file uninstantiated",input->name);
ali@9
   157
	    continue;
ali@9
   158
	}
ali@9
   159
	offset=0;
ali@9
   160
	do
ali@9
   161
	{
ali@9
   162
	    s=strstr(filename->str+offset,input->name);
ali@9
   163
	    if (s)
ali@9
   164
	    {
ali@9
   165
		pos=s-filename->str;
ali@9
   166
		g_string_overwrite(filename,pos,input->name_used);
ali@9
   167
		offset=pos+strlen(input->name);
ali@9
   168
	    }
ali@9
   169
	} while(s);
ali@9
   170
    }
ali@9
   171
    return g_string_free(filename,FALSE);
ali@9
   172
}
ali@9
   173
ali@9
   174
gboolean testcase_spawn_bookloupe(Testcase *testcase,char **standard_output,
ali@9
   175
  GError **error)
ali@7
   176
{
ali@7
   177
    gboolean r;
ali@9
   178
    int i,exit_status;
ali@9
   179
    char **argv;
ali@9
   180
    char *output,*s;
ali@7
   181
    GError *tmp_err=NULL;
ali@9
   182
    if (testcase->options)
ali@9
   183
	argv=g_new(char *,g_strv_length(testcase->options)+3);
ali@7
   184
    else
ali@9
   185
	argv=g_new(char *,3);
ali@9
   186
    s=getenv("BOOKLOUPE");
ali@9
   187
    if (!s)
ali@9
   188
	s="bookloupe";
ali@9
   189
    argv[0]=path_to_absolute(s);
ali@9
   190
    for(i=0;testcase->options && testcase->options[i];i++)
ali@9
   191
	argv[i+1]=testcase_resolve_input_files(testcase,testcase->options[i]);
ali@9
   192
    argv[i+1]=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
ali@9
   193
    argv[i+2]=NULL;
ali@7
   194
    if (standard_output)
ali@7
   195
    {
ali@9
   196
	r=spawn_sync(testcase->tmpdir,argv,&s,&exit_status,error);
ali@7
   197
	if (r)
ali@7
   198
	{
ali@9
   199
	    if (testcase->encoding)
ali@7
   200
	    {
ali@9
   201
		output=g_convert(s,-1,"UTF-8",testcase->encoding,NULL,NULL,
ali@9
   202
		  &tmp_err);
ali@7
   203
		g_free(s);
ali@7
   204
		if (!output)
ali@7
   205
		{
ali@7
   206
		    g_propagate_prefixed_error(error,tmp_err,
ali@9
   207
		      "Conversion from %s failed: ",testcase->encoding);
ali@7
   208
		    r=FALSE;
ali@7
   209
		}
ali@7
   210
	    }
ali@7
   211
	    else
ali@7
   212
	    {
ali@7
   213
		output=s;
ali@7
   214
		if (!g_utf8_validate(s,-1,NULL))
ali@7
   215
		{
ali@7
   216
		    g_set_error_literal(error,TESTCASE_ERROR,
ali@7
   217
		      TESTCASE_ERROR_FAILED,
ali@7
   218
		      "bookloupe output is not valid UTF-8");
ali@7
   219
		    r=FALSE;
ali@7
   220
		}
ali@7
   221
	    }
ali@7
   222
	}
ali@7
   223
    }
ali@7
   224
    else
ali@7
   225
    {
ali@9
   226
	r=spawn_sync(testcase->tmpdir,argv,NULL,&exit_status,error);
ali@7
   227
	output=NULL;
ali@7
   228
    }
ali@9
   229
    g_strfreev(argv);
ali@7
   230
    if (r && exit_status)
ali@7
   231
    {
ali@7
   232
	g_set_error(error,TESTCASE_ERROR,TESTCASE_ERROR_FAILED,
ali@7
   233
	  "bookloupe exited with code %d",exit_status);
ali@7
   234
	r=FALSE;
ali@7
   235
    }
ali@7
   236
    if (r && standard_output)
ali@7
   237
	*standard_output=output;
ali@7
   238
    return r;
ali@7
   239
}
ali@7
   240
ali@0
   241
/*
ali@0
   242
 * Run a testcase, returning FALSE on fail or error and
ali@0
   243
 * TRUE on pass or expected-fail.
ali@0
   244
 * Suitable message(s) will be printed in all cases.
ali@0
   245
 */
ali@6
   246
gboolean testcase_run(Testcase *testcase)
ali@0
   247
{
ali@6
   248
    gboolean r;
ali@7
   249
    size_t pos,offset;
ali@7
   250
    GString *header,*expected;
ali@7
   251
    char *output,*filename,*s;
ali@7
   252
    GError *error=NULL;
ali@9
   253
    if (!testcase_create_input_files(testcase,&error))
ali@9
   254
    {
ali@11
   255
	g_print("%s: FAIL\n",testcase->basename);
ali@11
   256
	g_print("%s\n",error->message);
ali@9
   257
	g_error_free(error);
ali@9
   258
	return FALSE;
ali@9
   259
    }
ali@7
   260
    if (testcase->expected)
ali@9
   261
	r=testcase_spawn_bookloupe(testcase,&output,&error);
ali@0
   262
    else
ali@0
   263
    {
ali@9
   264
	r=testcase_spawn_bookloupe(testcase,NULL,&error);
ali@9
   265
        output=NULL;
ali@7
   266
    }
ali@7
   267
    if (!r)
ali@7
   268
    {
ali@11
   269
	g_print("%s: FAIL\n",testcase->basename);
ali@11
   270
	g_print("%s\n",error->message);
ali@7
   271
	g_error_free(error);
ali@9
   272
	(void)testcase_remove_input_files(testcase,NULL);
ali@9
   273
	return FALSE;
ali@9
   274
    }
ali@9
   275
    filename=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
ali@9
   276
    if (!testcase_remove_input_files(testcase,&error))
ali@9
   277
    {
ali@11
   278
	g_print("%s: FAIL\n",testcase->basename);
ali@11
   279
	g_print("%s\n",error->message);
ali@9
   280
	g_error_free(error);
ali@0
   281
	return FALSE;
ali@0
   282
    }
ali@0
   283
    if (testcase->expected)
ali@0
   284
    {
ali@7
   285
	header=g_string_new("\n\nFile: ");
ali@7
   286
	g_string_append(header,filename);
ali@7
   287
	g_string_append(header,"\n");
ali@7
   288
	expected=g_string_new(testcase->expected);
ali@7
   289
	if (!g_str_has_prefix(output,header->str))
ali@7
   290
	{
ali@11
   291
	    g_print("%s: FAIL\n",testcase->basename);
ali@7
   292
	    offset=common_prefix_length(output,header->str);
ali@11
   293
	    g_print("Unexpected header from bookloupe:\n");
ali@7
   294
	    print_unexpected(output,offset);
ali@7
   295
	    r=FALSE;
ali@7
   296
	}
ali@7
   297
	pos=header->len;
ali@7
   298
	if (r)
ali@7
   299
	{
ali@7
   300
	    /* Skip the summary */
ali@7
   301
	    s=strstr(output+pos,"\n\n");
ali@7
   302
	    if (s)
ali@7
   303
		pos=s-output+2;
ali@7
   304
	    else
ali@7
   305
	    {
ali@11
   306
		g_print("%s: FAIL\n",testcase->basename);
ali@7
   307
		offset=common_prefix_length(output,header->str);
ali@11
   308
		g_print("Unterminated summary from bookloupe:\n%s\n",
ali@7
   309
		  output+pos);
ali@7
   310
		r=FALSE;
ali@7
   311
	    }
ali@7
   312
	}
ali@7
   313
	if (r && strcmp(output+pos,expected->str))
ali@7
   314
	{
ali@11
   315
	    g_print("%s: FAIL\n",testcase->basename);
ali@7
   316
	    offset=common_prefix_length(output+pos,expected->str);
ali@7
   317
	    if (!offset && !output[pos+offset])
ali@11
   318
		g_print("Unexpected zero warnings from bookloupe.\n");
ali@7
   319
	    else
ali@7
   320
	    {
ali@11
   321
		g_print("Unexpected output from bookloupe:\n");
ali@7
   322
		print_unexpected(output+pos,offset);
ali@7
   323
	    }
ali@7
   324
	    r=FALSE;
ali@7
   325
	}
ali@7
   326
	g_string_free(header,TRUE);
ali@7
   327
	g_string_free(expected,TRUE);
ali@0
   328
    }
ali@9
   329
    g_free(filename);
ali@6
   330
    g_free(output);
ali@7
   331
    if (r)
ali@11
   332
	g_print("%s: PASS\n",testcase->basename);
ali@7
   333
    return r;
ali@0
   334
}
ali@0
   335
ali@0
   336
/*
ali@0
   337
 * Free a testcase.
ali@0
   338
 */
ali@0
   339
void testcase_free(Testcase *testcase)
ali@0
   340
{
ali@6
   341
    g_free(testcase->basename);
ali@9
   342
    g_slist_foreach(testcase->inputs,(GFunc)testcase_input_free,NULL);
ali@9
   343
    g_slist_free(testcase->inputs);
ali@6
   344
    g_free(testcase->expected);
ali@7
   345
    g_free(testcase->encoding);
ali@9
   346
    g_strfreev(testcase->options);
ali@6
   347
    g_free(testcase);
ali@0
   348
}