Add a testcase for user-defined typos
authorali <ali@juiblex.co.uk>
Mon Jan 30 00:36:31 2012 +0000 (2012-01-30)
changeset 96a13fe0fc19e
parent 8 cf332d440466
child 10 f28ad4577863
Add a testcase for user-defined typos
bl/Makefile.am
bl/bl.h
bl/mkdtemp.c
bl/mkdtemp.h
bl/path.c
bl/path.h
bl/spawn.c
bl/spawn.h
configure.ac
test/compatibility/Makefile.am
test/compatibility/markup.tst
test/compatibility/user-defined-typo.tst
test/harness/Makefile.am
test/harness/testcase.c
test/harness/testcase.h
test/harness/testcaseinput.c
test/harness/testcaseinput.h
test/harness/testcaseio.c
     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 +&ldquo;He went <i>thataway!</i>&rdquo;
    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  }