1.1 --- a/bl/Makefile.am Fri Jan 27 23:59:51 2012 +0000
1.2 +++ b/bl/Makefile.am Mon Jan 30 00:36:31 2012 +0000
1.3 @@ -3,4 +3,5 @@
1.4 LIBS=$(GLIB_LIBS)
1.5
1.6 noinst_LTLIBRARIES=libbl.la
1.7 -libbl_la_SOURCES=bl.h textfileutils.c textfileutils.h spawn.c spawn.h
1.8 +libbl_la_SOURCES=bl.h textfileutils.c textfileutils.h spawn.c spawn.h \
1.9 + path.c path.h mkdtemp.c mkdtemp.h
2.1 --- a/bl/bl.h Fri Jan 27 23:59:51 2012 +0000
2.2 +++ b/bl/bl.h Mon Jan 30 00:36:31 2012 +0000
2.3 @@ -1,2 +1,4 @@
2.4 #include <bl/textfileutils.h>
2.5 #include <bl/spawn.h>
2.6 +#include <bl/path.h>
2.7 +#include <bl/mkdtemp.h>
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/bl/mkdtemp.c Mon Jan 30 00:36:31 2012 +0000
3.3 @@ -0,0 +1,35 @@
3.4 +#include <stdlib.h>
3.5 +#include <string.h>
3.6 +#include <errno.h>
3.7 +#include <glib.h>
3.8 +#include <glib/gstdio.h>
3.9 +#include "mkdtemp.h"
3.10 +
3.11 +#if !HAVE_G_MKDTEMP
3.12 +char *g_mkdtemp(char *template)
3.13 +{
3.14 +#if !defined(WIN32) && HAVE_MKDTEMP
3.15 + return mkdtemp(template);
3.16 +#else
3.17 + char *s;
3.18 + for(;;)
3.19 + {
3.20 + s=g_strdup(template);
3.21 + mktemp(s);
3.22 + if (!*s)
3.23 + {
3.24 + g_free(s);
3.25 + errno=EEXIST;
3.26 + return NULL;
3.27 + }
3.28 + if (g_mkdir(s,0700)>=0)
3.29 + {
3.30 + strcpy(template,s);
3.31 + g_free(s);
3.32 + return template;
3.33 + }
3.34 + g_free(s);
3.35 + }
3.36 +#endif /* !defined(WIN32) && HAVE_MKDTEMP */
3.37 +}
3.38 +#endif /* !HAVE_G_MKDTEMP */
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/bl/mkdtemp.h Mon Jan 30 00:36:31 2012 +0000
4.3 @@ -0,0 +1,8 @@
4.4 +#ifndef BL_MKDTEMP_H
4.5 +#define BL_MKDTEMP_H
4.6 +
4.7 +#if !HAVE_G_MKDTEMP
4.8 +char *g_mkdtemp(char *template);
4.9 +#endif
4.10 +
4.11 +#endif /* BL_MKDTEMP_H */
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/bl/path.c Mon Jan 30 00:36:31 2012 +0000
5.3 @@ -0,0 +1,53 @@
5.4 +#ifdef WIN32
5.5 +#include <windows.h>
5.6 +#endif
5.7 +#include <stdlib.h>
5.8 +#include <glib.h>
5.9 +#include <bl/bl.h>
5.10 +
5.11 +/*
5.12 + * Return an absolute path to <path>.
5.13 + * Note that this function makes no attempt to return a unique path, or
5.14 + * to remove "." or ".." entries. It simply returns a path which will
5.15 + * be unaffected by subsequent calls to chdir().
5.16 + */
5.17 +char *path_to_absolute(const char *path)
5.18 +{
5.19 +#ifdef WIN32
5.20 + long len;
5.21 + gunichar2 *path2;
5.22 + gunichar2 *abs2;
5.23 + char *abs;
5.24 + path2=g_utf8_to_utf16(path,-1,NULL,NULL,NULL);
5.25 + if (!path2)
5.26 + return NULL;
5.27 + len=GetFullPathNameW(path2,0,NULL,NULL); /* len includes nul */
5.28 + if (!len)
5.29 + {
5.30 + g_free(path2);
5.31 + return NULL;
5.32 + }
5.33 + abs2=g_new(gunichar2,len);
5.34 + len=GetFullPathNameW(path2,len,abs2,NULL); /* len excludes nul */
5.35 + g_free(path2);
5.36 + if (!len)
5.37 + {
5.38 + g_free(abs2);
5.39 + return NULL;
5.40 + }
5.41 + abs=g_utf16_to_utf8(abs2,len,NULL,NULL,NULL);
5.42 + g_free(abs2);
5.43 + return abs;
5.44 +#else
5.45 + char *s,*abs;
5.46 + if (*path=='/')
5.47 + abs=g_strdup(path);
5.48 + else
5.49 + {
5.50 + s=g_get_current_dir();
5.51 + abs=g_build_filename(s,path,NULL);
5.52 + g_free(s);
5.53 + }
5.54 + return abs;
5.55 +#endif
5.56 +}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/bl/path.h Mon Jan 30 00:36:31 2012 +0000
6.3 @@ -0,0 +1,6 @@
6.4 +#ifndef BL_PATH_H
6.5 +#define BL_PATH_H
6.6 +
6.7 +char *path_to_absolute(const char *path);
6.8 +
6.9 +#endif /* BL_PATH_H */
7.1 --- a/bl/spawn.c Fri Jan 27 23:59:51 2012 +0000
7.2 +++ b/bl/spawn.c Mon Jan 30 00:36:31 2012 +0000
7.3 @@ -7,8 +7,8 @@
7.4
7.5 #define SPAWN_BUFSIZE 128
7.6
7.7 -gboolean spawn_sync(char **argv,char **standard_output,int *exit_status,
7.8 - GError **error)
7.9 +gboolean spawn_sync(const char *working_directory,char **argv,
7.10 + char **standard_output,int *exit_status,GError **error)
7.11 {
7.12 /* Don't use g_spawn_sync on WIN32 for now to avoid needing the helper */
7.13 #ifndef WIN32
7.14 @@ -17,8 +17,8 @@
7.15 GSpawnFlags flags=G_SPAWN_SEARCH_PATH;
7.16 if (!standard_output)
7.17 flags=G_SPAWN_STDOUT_TO_DEV_NULL;
7.18 - retval=g_spawn_sync(NULL,argv,NULL,flags,NULL,NULL,standard_output,
7.19 - &standard_error,exit_status,error);
7.20 + retval=g_spawn_sync(working_directory,argv,NULL,flags,NULL,NULL,
7.21 + standard_output,&standard_error,exit_status,error);
7.22 if (standard_error)
7.23 fputs(standard_error,stderr);
7.24 g_free(standard_error);
7.25 @@ -29,7 +29,21 @@
7.26 FILE *fp;
7.27 int i,r;
7.28 size_t n,len;
7.29 + char *current_dir;
7.30 GString *command_line,*string;
7.31 + if (working_directory)
7.32 + {
7.33 + current_dir=g_get_current_dir();
7.34 + if (g_chdir(working_directory)<0)
7.35 + {
7.36 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
7.37 + "%s: %s",working_directory,g_strerror(errno));
7.38 + g_free(current_dir);
7.39 + return FALSE;
7.40 + }
7.41 + }
7.42 + else
7.43 + current_dir=NULL;
7.44 command_line=g_string_new(NULL);
7.45 for(i=0;argv[i];i++)
7.46 {
7.47 @@ -39,6 +53,13 @@
7.48 }
7.49 fp=popen(command_line->str,"r");
7.50 g_string_free(command_line,TRUE);
7.51 + if (current_dir)
7.52 + {
7.53 + if (g_chdir(current_dir)<0)
7.54 + g_error("Failed to restore current directory: %s",
7.55 + g_strerror(errno));
7.56 + g_free(current_dir);
7.57 + }
7.58 if (!fp)
7.59 {
7.60 g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
8.1 --- a/bl/spawn.h Fri Jan 27 23:59:51 2012 +0000
8.2 +++ b/bl/spawn.h Mon Jan 30 00:36:31 2012 +0000
8.3 @@ -3,7 +3,7 @@
8.4
8.5 #include <glib.h>
8.6
8.7 -gboolean spawn_sync(char **argv,char **standard_output,int *exit_status,
8.8 - GError **error);
8.9 +gboolean spawn_sync(const char *working_directory,char **argv,
8.10 + char **standard_output,int *exit_status,GError **error);
8.11
8.12 #endif /* BL_SPAWN_H */
9.1 --- a/configure.ac Fri Jan 27 23:59:51 2012 +0000
9.2 +++ b/configure.ac Mon Jan 30 00:36:31 2012 +0000
9.3 @@ -45,6 +45,9 @@
9.4 AS_IF([test "$enable_shared" = no],[
9.5 LDFLAGS="$LDFLAGS -static-libtool-libs"
9.6 ])
9.7 +LT_OUTPUT
9.8 +ac_compile="./libtool --mode=compile --tag=CC $ac_compile"
9.9 +ac_link="./libtool --mode=link --tag=CC $ac_link"
9.10 PKG_PROG_PKG_CONFIG
9.11
9.12 ##################################################
9.13 @@ -60,15 +63,6 @@
9.14 ##################################################
9.15 PKG_CHECK_MODULES([GLIB],[glib-2.0])
9.16
9.17 -# NOTE: If we are using a static version of glib then we
9.18 -# should define GLIB_STATIC_COMPILATION. This isn't needed
9.19 -# when glib is built only for static use (in which case
9.20 -# glibconfig.h will already define GLIB_STATIC_COMPILATION).
9.21 -# It's not easy to tell if libtool will actually link with
9.22 -# a static glib but luckily we don't currently need to;
9.23 -# this pre-processor define only affects the behaviour of
9.24 -# libraries which use glib and we don't have any.
9.25 -
9.26 # Glib 2.30 (at least) doesn't include "-framework Carbon" in Libs.private
9.27 # which causes link to fail for static glib on Mac (gnome bug #668152).
9.28 # Test for this problem and implement a workaround.
9.29 @@ -97,10 +91,39 @@
9.30 ])
9.31 ])
9.32
9.33 +# If we are using a static version of glib then we should define
9.34 +# GLIB_STATIC_COMPILATION. This isn't needed when glib is built
9.35 +# only for static use (in which case glibconfig.h will already
9.36 +# define GLIB_STATIC_COMPILATION).
9.37 +AS_IF([test "$enable_shared" = no],[
9.38 + AC_MSG_CHECKING([whether GLIB_STATIC_COMPILATION needs to be defined])
9.39 + save_CFLAGS="$CFLAGS"
9.40 + save_LIBS="$LIBS"
9.41 + CFLAGS="$GLIB_CFLAGS"
9.42 + LIBS="$GLIB_LIBS"
9.43 + AC_TRY_LINK([
9.44 + #include <glib.h>
9.45 + ],[
9.46 + return glib_major_version!=2;
9.47 + ],[static_compilation=no],[static_compilation=yes])
9.48 + CFLAGS="$save_CFLAGS"
9.49 + LIBS="$save_LIBS"
9.50 + AC_MSG_RESULT([$static_compilation])
9.51 + AS_IF([test "$static_compilation" = yes],[
9.52 + GLIB_CFLAGS="$GLIB_CFLAGS -DGLIB_STATIC_COMPILATION"
9.53 + ])
9.54 +])
9.55 +
9.56 ##################################################
9.57 # Checks for library functions.
9.58 ##################################################
9.59 -AC_CHECK_FUNCS_ONCE([mkstemp])
9.60 +save_CFLAGS="$CFLAGS"
9.61 +save_LIBS="$LIBS"
9.62 +CFLAGS="$GLIB_CFLAGS"
9.63 +LIBS="$GLIB_LIBS"
9.64 +AC_CHECK_FUNCS([g_mkdtemp mkdtemp],[break])
9.65 +CFLAGS="$save_CFLAGS"
9.66 +LIBS="$save_LIBS"
9.67
9.68 ##################################################
9.69 # Checks for processor independent files.
10.1 --- a/test/compatibility/Makefile.am Fri Jan 27 23:59:51 2012 +0000
10.2 +++ b/test/compatibility/Makefile.am Mon Jan 30 00:36:31 2012 +0000
10.3 @@ -2,6 +2,7 @@
10.4 TESTS=missing-space.tst spaced-punctuation.tst html-tag.tst html-symbol.tst \
10.5 spaced-doublequote.tst mismatched-quotes.tst he-be.tst digits.tst \
10.6 extra-period.tst ellipsis.tst short-line.tst abbreviation.tst \
10.7 - example.tst non-ascii.tst embedded-lf.tst
10.8 + example.tst non-ascii.tst embedded-lf.tst markup.tst \
10.9 + user-defined-typo.tst
10.10
10.11 dist_pkgdata_DATA=$(TESTS)
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/test/compatibility/markup.tst Mon Jan 30 00:36:31 2012 +0000
11.3 @@ -0,0 +1,6 @@
11.4 +**************** OPTIONS ****************
11.5 +-m
11.6 +-d
11.7 +**************** INPUT ****************
11.8 +“He went <i>thataway!</i>”
11.9 +**************** EXPECTED ****************
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/test/compatibility/user-defined-typo.tst Mon Jan 30 00:36:31 2012 +0000
12.3 @@ -0,0 +1,17 @@
12.4 +**************** OPTIONS ****************
12.5 +-u
12.6 +**************** INPUT(gutcheck.typ) ****************
12.7 +arid
12.8 +**************** INPUT ****************
12.9 +I am the very model of a modern Major-General,
12.10 +I've information vegetable, animal, and mineral,
12.11 +I know the kings of England, arid I quote the fights historical
12.12 +From Marathon to Waterloo, in order categorical;
12.13 +I'm very well acquainted, too, with matters mathematical,
12.14 +I understand equations, both the simple and quadratical,
12.15 +About binomial theorem I'm teeming with a lot o' news--
12.16 +With many cheerful facts about the square of the hypotenuse.
12.17 +**************** EXPECTED ****************
12.18 +
12.19 +I know the kings of England, arid I quote the fights historical
12.20 + Line 3 column 29 - Query possible scanno arid
13.1 --- a/test/harness/Makefile.am Fri Jan 27 23:59:51 2012 +0000
13.2 +++ b/test/harness/Makefile.am Mon Jan 30 00:36:31 2012 +0000
13.3 @@ -4,5 +4,6 @@
13.4 LIBS=$(GLIB_LIBS)
13.5
13.6 loupe_test_SOURCES=loupe-test.c testcase.c testcase.h testcaseio.c \
13.7 - testcaseio.h testcaseparser.c testcaseparser.h
13.8 + testcaseio.h testcaseparser.c testcaseparser.h testcaseinput.c \
13.9 + testcaseinput.h
13.10 loupe_test_LDADD=../../bl/libbl.la
14.1 --- a/test/harness/testcase.c Fri Jan 27 23:59:51 2012 +0000
14.2 +++ b/test/harness/testcase.c Mon Jan 30 00:36:31 2012 +0000
14.3 @@ -1,10 +1,12 @@
14.4 #include <stdlib.h>
14.5 #include <stdio.h>
14.6 #include <string.h>
14.7 -#include <unistd.h>
14.8 #include <errno.h>
14.9 +#include <glib.h>
14.10 +#include <glib/gstdio.h>
14.11 #include <bl/bl.h>
14.12 #include "testcase.h"
14.13 +#include "testcaseinput.h"
14.14
14.15 GQuark testcase_error_quark(void)
14.16 {
14.17 @@ -12,20 +14,6 @@
14.18 }
14.19
14.20 /*
14.21 - * As write(), but with error handling.
14.22 - */
14.23 -static size_t write_file(int fd,const char *buf,size_t count,GError **error)
14.24 -{
14.25 - if (write(fd,buf,count)<count)
14.26 - {
14.27 - g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
14.28 - "Error writing bookloupe input file: %s",g_strerror(errno));
14.29 - return -1;
14.30 - }
14.31 - return count;
14.32 -}
14.33 -
14.34 -/*
14.35 * Return the length (in bytes) of any common prefix between s1 and s2.
14.36 * The returned length will always represent an exact number of characters.
14.37 */
14.38 @@ -79,91 +67,144 @@
14.39 }
14.40
14.41 /*
14.42 - * Replace \n with \r\n and U+240A (visible symbol for LF) with \n
14.43 + * Create all the input files needed by a testcase and, if required,
14.44 + * a temporary directory in which to store them.
14.45 */
14.46 -char *unix2dos(const char *text)
14.47 +gboolean testcase_create_input_files(Testcase *testcase,GError **error)
14.48 {
14.49 - gunichar c;
14.50 - const gunichar visible_lf=0x240A;
14.51 - GString *string;
14.52 - string=g_string_new(NULL);
14.53 - while(*text)
14.54 + GSList *link,*link2;
14.55 + if (testcase->flags&TESTCASE_TMP_DIR)
14.56 {
14.57 - c=g_utf8_get_char(text);
14.58 - text=g_utf8_next_char(text);
14.59 - if (c=='\n')
14.60 - g_string_append(string,"\r\n");
14.61 - else if (c==visible_lf)
14.62 - g_string_append_c(string,'\n');
14.63 - else
14.64 - g_string_append_unichar(string,c);
14.65 + testcase->tmpdir=g_strdup("TEST-XXXXXX");
14.66 + if (!g_mkdtemp(testcase->tmpdir))
14.67 + {
14.68 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
14.69 + "Failed to create temporary directory: %s",g_strerror(errno));
14.70 + g_free(testcase->tmpdir);
14.71 + testcase->tmpdir=NULL;
14.72 + return FALSE;
14.73 + }
14.74 }
14.75 - return g_string_free(string,FALSE);
14.76 + for(link=testcase->inputs;link;link=link->next)
14.77 + if (!testcase_input_create(testcase,link->data,error))
14.78 + {
14.79 + for(link2=testcase->inputs;link2!=link;link2=link2->next)
14.80 + (void)testcase_input_remove(testcase,link2->data,NULL);
14.81 + if (testcase->tmpdir)
14.82 + {
14.83 + (void)g_rmdir(testcase->tmpdir);
14.84 + g_free(testcase->tmpdir);
14.85 + testcase->tmpdir=NULL;
14.86 + }
14.87 + return FALSE;
14.88 + }
14.89 + return TRUE;
14.90 }
14.91
14.92 -gboolean spawn_bootloupe(const char *encoding,const char *standard_input,
14.93 - char **standard_output,char **filename,GError **error)
14.94 +/*
14.95 + * Remove all the input files used by a testcase and, if created,
14.96 + * the temporary directory in which they are stored.
14.97 + */
14.98 +gboolean testcase_remove_input_files(Testcase *testcase,GError **error)
14.99 +{
14.100 + GSList *link;
14.101 + GError *tmp_err=NULL;
14.102 + gboolean retval=TRUE;
14.103 + for(link=testcase->inputs;link;link=link->next)
14.104 + if (!testcase_input_remove(testcase,link->data,&tmp_err))
14.105 + {
14.106 + if (error && !*error)
14.107 + g_propagate_error(error,tmp_err);
14.108 + else
14.109 + g_clear_error(&tmp_err);
14.110 + retval=FALSE;
14.111 + }
14.112 + if (testcase->tmpdir)
14.113 + {
14.114 + if (g_rmdir(testcase->tmpdir))
14.115 + {
14.116 + if (error && !*error)
14.117 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
14.118 + "Failed to remove temporary directory: %s",g_strerror(errno));
14.119 + retval=FALSE;
14.120 + }
14.121 + g_free(testcase->tmpdir);
14.122 + testcase->tmpdir=NULL;
14.123 + }
14.124 + return retval;
14.125 +}
14.126 +
14.127 +/*
14.128 + * Replace every occurance of an input file name in <str> with the
14.129 + * filename which holds that input. For input files with fixed names,
14.130 + * this is a noop. For input files which use the "XXXXXX" sequence
14.131 + * to create a unique filename, the XXXXXX will be replaced with the
14.132 + * 6 characters that were chosen to be unique.
14.133 + */
14.134 +char *testcase_resolve_input_files(Testcase *testcase,const char *str)
14.135 +{
14.136 + GSList *link;
14.137 + gsize offset,pos;
14.138 + char *s;
14.139 + TestcaseInput *input;
14.140 + GString *filename=g_string_new(str);
14.141 + for(link=testcase->inputs;link;link=link->next)
14.142 + {
14.143 + input=link->data;
14.144 + if (!input->name_used)
14.145 + {
14.146 + g_warning("%s: Input file uninstantiated",input->name);
14.147 + continue;
14.148 + }
14.149 + offset=0;
14.150 + do
14.151 + {
14.152 + s=strstr(filename->str+offset,input->name);
14.153 + if (s)
14.154 + {
14.155 + pos=s-filename->str;
14.156 + g_string_overwrite(filename,pos,input->name_used);
14.157 + offset=pos+strlen(input->name);
14.158 + }
14.159 + } while(s);
14.160 + }
14.161 + return g_string_free(filename,FALSE);
14.162 +}
14.163 +
14.164 +gboolean testcase_spawn_bookloupe(Testcase *testcase,char **standard_output,
14.165 + GError **error)
14.166 {
14.167 gboolean r;
14.168 - int fd,exit_status;
14.169 - size_t n,pos,offset;
14.170 - char input[]="TEST-XXXXXX";
14.171 - char *command[3];
14.172 - char *output,*s,*t;
14.173 + int i,exit_status;
14.174 + char **argv;
14.175 + char *output,*s;
14.176 GError *tmp_err=NULL;
14.177 - if (standard_input)
14.178 - {
14.179 - if (encoding)
14.180 - {
14.181 - t=unix2dos(standard_input);
14.182 - s=g_convert(t,-1,encoding,"UTF-8",NULL,&n,&tmp_err);
14.183 - g_free(t);
14.184 - if (!s)
14.185 - {
14.186 - g_propagate_prefixed_error(error,tmp_err,
14.187 - "Conversion to %s failed: ",encoding);
14.188 - return FALSE;
14.189 - }
14.190 - }
14.191 - else
14.192 - {
14.193 - s=unix2dos(standard_input);
14.194 - n=strlen(s);
14.195 - }
14.196 - }
14.197 + if (testcase->options)
14.198 + argv=g_new(char *,g_strv_length(testcase->options)+3);
14.199 else
14.200 - {
14.201 - n=0;
14.202 - s=NULL;
14.203 - }
14.204 - fd=g_mkstemp(input);
14.205 - if (n && write_file(fd,s,n,error)!=n)
14.206 - {
14.207 - g_free(s);
14.208 - close(fd);
14.209 - (void)remove(input);
14.210 - return FALSE;
14.211 - }
14.212 - g_free(s);
14.213 - close(fd);
14.214 - command[0]=getenv("BOOKLOUPE");
14.215 - if (!command[0])
14.216 - command[0]="." G_DIR_SEPARATOR_S "bookloupe";
14.217 - command[1]=input;
14.218 - command[2]=NULL;
14.219 + argv=g_new(char *,3);
14.220 + s=getenv("BOOKLOUPE");
14.221 + if (!s)
14.222 + s="bookloupe";
14.223 + argv[0]=path_to_absolute(s);
14.224 + for(i=0;testcase->options && testcase->options[i];i++)
14.225 + argv[i+1]=testcase_resolve_input_files(testcase,testcase->options[i]);
14.226 + argv[i+1]=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
14.227 + argv[i+2]=NULL;
14.228 if (standard_output)
14.229 {
14.230 - r=spawn_sync(command,&s,&exit_status,error);
14.231 + r=spawn_sync(testcase->tmpdir,argv,&s,&exit_status,error);
14.232 if (r)
14.233 {
14.234 - if (encoding)
14.235 + if (testcase->encoding)
14.236 {
14.237 - output=g_convert(s,-1,"UTF-8",encoding,NULL,NULL,&tmp_err);
14.238 + output=g_convert(s,-1,"UTF-8",testcase->encoding,NULL,NULL,
14.239 + &tmp_err);
14.240 g_free(s);
14.241 if (!output)
14.242 {
14.243 g_propagate_prefixed_error(error,tmp_err,
14.244 - "Conversion from %s failed: ",encoding);
14.245 + "Conversion from %s failed: ",testcase->encoding);
14.246 r=FALSE;
14.247 }
14.248 }
14.249 @@ -182,18 +223,16 @@
14.250 }
14.251 else
14.252 {
14.253 - r=spawn_sync(command,NULL,&exit_status,error);
14.254 + r=spawn_sync(testcase->tmpdir,argv,NULL,&exit_status,error);
14.255 output=NULL;
14.256 }
14.257 - (void)remove(input);
14.258 + g_strfreev(argv);
14.259 if (r && exit_status)
14.260 {
14.261 g_set_error(error,TESTCASE_ERROR,TESTCASE_ERROR_FAILED,
14.262 "bookloupe exited with code %d",exit_status);
14.263 r=FALSE;
14.264 }
14.265 - if (r && filename)
14.266 - *filename=g_strdup(input);
14.267 if (r && standard_output)
14.268 *standard_output=output;
14.269 return r;
14.270 @@ -211,19 +250,34 @@
14.271 GString *header,*expected;
14.272 char *output,*filename,*s;
14.273 GError *error=NULL;
14.274 + if (!testcase_create_input_files(testcase,&error))
14.275 + {
14.276 + fprintf(stderr,"%s: FAIL\n",testcase->basename);
14.277 + fprintf(stderr,"%s\n",error->message);
14.278 + g_error_free(error);
14.279 + return FALSE;
14.280 + }
14.281 if (testcase->expected)
14.282 - r=spawn_bootloupe(testcase->encoding,testcase->input,&output,&filename,
14.283 - &error);
14.284 + r=testcase_spawn_bookloupe(testcase,&output,&error);
14.285 else
14.286 {
14.287 - r=spawn_bootloupe(testcase->encoding,testcase->input,NULL,NULL,&error);
14.288 - output=filename=NULL;
14.289 + r=testcase_spawn_bookloupe(testcase,NULL,&error);
14.290 + output=NULL;
14.291 }
14.292 if (!r)
14.293 {
14.294 fprintf(stderr,"%s: FAIL\n",testcase->basename);
14.295 fprintf(stderr,"%s\n",error->message);
14.296 g_error_free(error);
14.297 + (void)testcase_remove_input_files(testcase,NULL);
14.298 + return FALSE;
14.299 + }
14.300 + filename=testcase_resolve_input_files(testcase,"TEST-XXXXXX");
14.301 + if (!testcase_remove_input_files(testcase,&error))
14.302 + {
14.303 + fprintf(stderr,"%s: FAIL\n",testcase->basename);
14.304 + fprintf(stderr,"%s\n",error->message);
14.305 + g_error_free(error);
14.306 return FALSE;
14.307 }
14.308 if (testcase->expected)
14.309 @@ -272,8 +326,8 @@
14.310 g_string_free(header,TRUE);
14.311 g_string_free(expected,TRUE);
14.312 }
14.313 + g_free(filename);
14.314 g_free(output);
14.315 - g_free(filename);
14.316 if (r)
14.317 fprintf(stderr,"%s: PASS\n",testcase->basename);
14.318 return r;
14.319 @@ -285,8 +339,10 @@
14.320 void testcase_free(Testcase *testcase)
14.321 {
14.322 g_free(testcase->basename);
14.323 - g_free(testcase->input);
14.324 + g_slist_foreach(testcase->inputs,(GFunc)testcase_input_free,NULL);
14.325 + g_slist_free(testcase->inputs);
14.326 g_free(testcase->expected);
14.327 g_free(testcase->encoding);
14.328 + g_strfreev(testcase->options);
14.329 g_free(testcase);
14.330 }
15.1 --- a/test/harness/testcase.h Fri Jan 27 23:59:51 2012 +0000
15.2 +++ b/test/harness/testcase.h Mon Jan 30 00:36:31 2012 +0000
15.3 @@ -11,11 +11,14 @@
15.4
15.5 typedef struct {
15.6 char *basename;
15.7 - char *input;
15.8 + char *tmpdir;
15.9 + GSList *inputs;
15.10 char *expected;
15.11 char *encoding; /* The character encoding to talk to BOOKLOUPE in */
15.12 + char **options;
15.13 enum {
15.14 TESTCASE_XFAIL=1<<0,
15.15 + TESTCASE_TMP_DIR=1<<1,
15.16 } flags;
15.17 } Testcase;
15.18
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/test/harness/testcaseinput.c Mon Jan 30 00:36:31 2012 +0000
16.3 @@ -0,0 +1,182 @@
16.4 +#include <stdlib.h>
16.5 +#include <stdio.h>
16.6 +#include <string.h>
16.7 +#include <unistd.h>
16.8 +#include <errno.h>
16.9 +#include <fcntl.h>
16.10 +#ifdef WIN32
16.11 +#include <io.h>
16.12 +#endif
16.13 +#include <glib.h>
16.14 +#include <glib/gstdio.h>
16.15 +#include <bl/bl.h>
16.16 +#include "testcase.h"
16.17 +#include "testcaseinput.h"
16.18 +
16.19 +#ifndef O_BINARY
16.20 +#define O_BINARY 0
16.21 +#endif
16.22 +
16.23 +/*
16.24 + * As write(), but with error handling.
16.25 + */
16.26 +static size_t write_file(int fd,const char *buf,size_t count,GError **error)
16.27 +{
16.28 + if (write(fd,buf,count)<count)
16.29 + {
16.30 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
16.31 + "Error writing bookloupe input file: %s",g_strerror(errno));
16.32 + return -1;
16.33 + }
16.34 + return count;
16.35 +}
16.36 +
16.37 +/*
16.38 + * Replace \n with \r\n and U+240A (visible symbol for LF) with \n
16.39 + */
16.40 +static char *unix2dos(const char *text)
16.41 +{
16.42 + gunichar c;
16.43 + const gunichar visible_lf=0x240A;
16.44 + GString *string;
16.45 + string=g_string_new(NULL);
16.46 + while(*text)
16.47 + {
16.48 + c=g_utf8_get_char(text);
16.49 + text=g_utf8_next_char(text);
16.50 + if (c=='\n')
16.51 + g_string_append(string,"\r\n");
16.52 + else if (c==visible_lf)
16.53 + g_string_append_c(string,'\n');
16.54 + else
16.55 + g_string_append_unichar(string,c);
16.56 + }
16.57 + return g_string_free(string,FALSE);
16.58 +}
16.59 +
16.60 +/*
16.61 + * Create an input file needed for a testcase (as specified in <input>).
16.62 + * The file is written in the encoding specified for communicating with
16.63 + * bookloupe. The name_used field of <input> is filled in with the name
16.64 + * of the created file (which may be different than the name specified
16.65 + * if that contained "XXXXXX" to be replaced by a unique string).
16.66 + */
16.67 +gboolean testcase_input_create(Testcase *testcase,TestcaseInput *input,
16.68 + GError **error)
16.69 +{
16.70 + int fd;
16.71 + size_t n;
16.72 + char *filename,*s,*t;
16.73 + GError *tmp_err=NULL;
16.74 + if (input->contents)
16.75 + {
16.76 + if (testcase->encoding)
16.77 + {
16.78 + t=unix2dos(input->contents);
16.79 + s=g_convert(t,-1,testcase->encoding,"UTF-8",NULL,&n,&tmp_err);
16.80 + g_free(t);
16.81 + if (!s)
16.82 + {
16.83 + g_propagate_prefixed_error(error,tmp_err,
16.84 + "Conversion to %s failed: ",testcase->encoding);
16.85 + return FALSE;
16.86 + }
16.87 + }
16.88 + else
16.89 + {
16.90 + s=unix2dos(input->contents);
16.91 + n=strlen(s);
16.92 + }
16.93 + }
16.94 + else
16.95 + {
16.96 + n=0;
16.97 + s=NULL;
16.98 + }
16.99 + g_free(input->name_used);
16.100 + input->name_used=NULL;
16.101 + if (testcase->tmpdir)
16.102 + filename=g_build_filename(testcase->tmpdir,input->name,NULL);
16.103 + else
16.104 + filename=g_strdup(input->name);
16.105 + if (strstr(input->name,"XXXXXX"))
16.106 + fd=g_mkstemp(filename);
16.107 + else
16.108 + fd=g_open(filename,O_WRONLY|O_CREAT|O_EXCL|O_BINARY,0600);
16.109 + if (fd<0)
16.110 + {
16.111 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
16.112 + "%s: %s",filename,g_strerror(errno));
16.113 + g_free(s);
16.114 + return FALSE;
16.115 + }
16.116 + input->name_used=g_strdup(filename+strlen(filename)-strlen(input->name));
16.117 + if (n && write_file(fd,s,n,error)!=n)
16.118 + {
16.119 + g_free(s);
16.120 + close(fd);
16.121 + (void)g_unlink(filename);
16.122 + g_free(filename);
16.123 + g_free(input->name_used);
16.124 + input->name_used=NULL;
16.125 + return FALSE;
16.126 + }
16.127 + g_free(s);
16.128 + if (close(fd)<0)
16.129 + {
16.130 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
16.131 + "%s: %s",filename,g_strerror(errno));
16.132 + (void)g_unlink(filename);
16.133 + g_free(filename);
16.134 + g_free(input->name_used);
16.135 + input->name_used=NULL;
16.136 + return FALSE;
16.137 + }
16.138 + g_free(filename);
16.139 + return TRUE;
16.140 +}
16.141 +
16.142 +/*
16.143 + * Remove an input file created with testcase_input_create()
16.144 + */
16.145 +gboolean testcase_input_remove(Testcase *testcase,TestcaseInput *input,
16.146 + GError **error)
16.147 +{
16.148 + char *filename;
16.149 + if (input->name_used)
16.150 + {
16.151 + if (testcase->tmpdir)
16.152 + filename=g_build_filename(testcase->tmpdir,input->name_used,NULL);
16.153 + else
16.154 + filename=g_strdup(input->name_used);
16.155 + if (g_unlink(filename)<0)
16.156 + {
16.157 + g_set_error(error,G_FILE_ERROR,g_file_error_from_errno(errno),
16.158 + "%s: %s",filename,g_strerror(errno));
16.159 + return FALSE;
16.160 + }
16.161 + g_free(filename);
16.162 + g_free(input->name_used);
16.163 + input->name_used=NULL;
16.164 + }
16.165 + return TRUE;
16.166 +}
16.167 +
16.168 +/* Create a new description of an input file needed for a testcase */
16.169 +TestcaseInput *testcase_input_new(const char *name,const char *contents)
16.170 +{
16.171 + TestcaseInput *input;
16.172 + input=g_new0(TestcaseInput,1);
16.173 + input->name=g_strdup(name);
16.174 + input->contents=g_strdup(contents);
16.175 + return input;
16.176 +}
16.177 +
16.178 +/* Free the description of a testcase input file */
16.179 +void testcase_input_free(TestcaseInput *input)
16.180 +{
16.181 + g_free(input->name);
16.182 + g_free(input->name_used);
16.183 + g_free(input->contents);
16.184 + g_free(input);
16.185 +}
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
17.2 +++ b/test/harness/testcaseinput.h Mon Jan 30 00:36:31 2012 +0000
17.3 @@ -0,0 +1,20 @@
17.4 +#ifndef TESTCASE_INPUT_H
17.5 +#define TESTCASE_INPUT_H
17.6 +
17.7 +#include <glib.h>
17.8 +#include "testcase.h"
17.9 +
17.10 +typedef struct {
17.11 + char *name;
17.12 + char *name_used;
17.13 + char *contents;
17.14 +} TestcaseInput;
17.15 +
17.16 +gboolean testcase_input_create(Testcase *testcase,TestcaseInput *input,
17.17 + GError **error);
17.18 +gboolean testcase_input_remove(Testcase *testcase,TestcaseInput *input,
17.19 + GError **error);
17.20 +TestcaseInput *testcase_input_new(const char *name,const char *contents);
17.21 +void testcase_input_free(TestcaseInput *input);
17.22 +
17.23 +#endif /* TESTCASE_INPUT_H */
18.1 --- a/test/harness/testcaseio.c Fri Jan 27 23:59:51 2012 +0000
18.2 +++ b/test/harness/testcaseio.c Mon Jan 30 00:36:31 2012 +0000
18.3 @@ -4,6 +4,7 @@
18.4 #include <glib.h>
18.5 #include <bl/bl.h>
18.6 #include "testcaseparser.h"
18.7 +#include "testcaseinput.h"
18.8 #include "testcaseio.h"
18.9
18.10 /*
18.11 @@ -15,7 +16,8 @@
18.12 {
18.13 Testcase *testcase;
18.14 TestcaseParser *parser;
18.15 - char *s;
18.16 + TestcaseInput *input=NULL;
18.17 + char *s,*arg;
18.18 const char *tag,*text;
18.19 gboolean found_tag=FALSE;
18.20 parser=testcase_parser_new_from_file(filename);
18.21 @@ -34,12 +36,44 @@
18.22 *s='\0';
18.23 while(testcase_parser_get_next_tag(parser,&tag,&text))
18.24 {
18.25 - if (!testcase->input && !strcmp(tag,"INPUT"))
18.26 - testcase->input=g_strdup(text);
18.27 + if (!input && !strcmp(tag,"INPUT"))
18.28 + input=testcase_input_new("TEST-XXXXXX",text);
18.29 + else if (g_str_has_prefix(tag,"INPUT(") && tag[strlen(tag)-1]==')')
18.30 + {
18.31 + arg=g_strndup(tag+6,strlen(tag)-7);
18.32 + s=g_path_get_dirname(arg);
18.33 + if (strcmp(s,"."))
18.34 + {
18.35 + /*
18.36 + * Otherwise it would be possible to overwrite an arbitary
18.37 + * file on somebody's computer by getting them to run a
18.38 + * testcase!
18.39 + */
18.40 + fprintf(stderr,
18.41 + "%s: Input files may not have a directory component\n",arg);
18.42 + g_free(s);
18.43 + g_free(arg);
18.44 + testcase_free(testcase);
18.45 + testcase_parser_free(parser);
18.46 + return NULL;
18.47 + }
18.48 + g_free(s);
18.49 + testcase->inputs=g_slist_prepend(testcase->inputs,
18.50 + testcase_input_new(arg,text));
18.51 + if (!strstr(arg,"XXXXXX"))
18.52 + testcase->flags|=TESTCASE_TMP_DIR;
18.53 + g_free(arg);
18.54 + }
18.55 else if (!testcase->expected && !strcmp(tag,"EXPECTED"))
18.56 testcase->expected=g_strdup(text);
18.57 else if (!testcase->encoding && !strcmp(tag,"ENCODING"))
18.58 testcase->encoding=g_strchomp(g_strdup(text));
18.59 + else if (!testcase->encoding && !strcmp(tag,"OPTIONS"))
18.60 + {
18.61 + testcase->options=g_strsplit(text,"\n",0);
18.62 + g_free(testcase->options[g_strv_length(testcase->options)-1]);
18.63 + testcase->options[g_strv_length(testcase->options)-1]=NULL;
18.64 + }
18.65 else
18.66 {
18.67 fprintf(stderr,"%s: Not a valid testcase (%s)\n",filename,tag);
18.68 @@ -61,6 +95,9 @@
18.69 testcase_parser_free(parser);
18.70 return NULL;
18.71 }
18.72 + if (!input)
18.73 + input=testcase_input_new("TEST-XXXXXX",NULL);
18.74 + testcase->inputs=g_slist_prepend(testcase->inputs,input);
18.75 testcase_parser_free(parser);
18.76 return testcase;
18.77 }