Rename test/ to test/api/
diff --git a/test/api/Makefile.am b/test/api/Makefile.am
new file mode 100644
index 0000000..b3c8cc2
--- /dev/null
+++ b/test/api/Makefile.am
@@ -0,0 +1,131 @@
+# Process this file with automake to produce Makefile.in
+
+NULL =
+EXTRA_DIST =
+CLEANFILES =
+DISTCLEANFILES =
+MAINTAINERCLEANFILES =
+
+if HAVE_GLIB
+AM_CPPFLAGS = -DSRCDIR="\"$(srcdir)\"" -I$(top_srcdir)/src/ -I$(top_builddir)/src/ $(GLIB_CFLAGS) $(GTHREAD_CFLAGS)
+LDADD = $(top_builddir)/src/libharfbuzz.la $(GLIB_LIBS) $(GTHREAD_LIBS)
+
+EXTRA_DIST += hb-test.h
+
+check_PROGRAMS = $(TEST_PROGS)
+noinst_PROGRAMS = $(TEST_PROGS)
+
+TEST_PROGS = \
+	test-blob \
+	test-buffer \
+	test-common \
+	test-font \
+	test-object \
+	test-shape \
+	test-unicode \
+	test-version \
+	$(NULL)
+
+if HAVE_OT
+TEST_PROGS += \
+	test-ot-tag \
+	$(NULL)
+endif
+
+# Tests for header compilation
+TEST_PROGS += \
+	test-c \
+	test-cplusplus \
+	$(NULL)
+test_cplusplus_SOURCES = test-cplusplus.cc
+test_c_CPPFLAGS = $(AM_CPPFLAGS)
+test_cplusplus_CPPFLAGS = $(AM_CPPFLAGS)
+if HAVE_ICU
+test_c_CPPFLAGS += $(ICU_CFLAGS)
+test_cplusplus_CPPFLAGS += $(ICU_CFLAGS)
+endif
+if HAVE_FREETYPE
+test_c_CPPFLAGS += $(FREETYPE_CFLAGS)
+test_cplusplus_CPPFLAGS += $(FREETYPE_CFLAGS)
+endif
+
+if HAVE_FREETYPE
+TEST_PROGS += test-shape-complex
+test_shape_complex_CPPFLAGS = $(AM_CPPFLAGS) $(FREETYPE_CFLAGS)
+test_shape_complex_LDADD = $(LDADD) $(FREETYPE_LIBS)
+endif
+
+
+# Default test running environment
+TESTS = $(TEST_PROGS)
+TESTS_ENVIRONMENT = \
+	MALLOC_CHECK_=2 \
+	MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256)) \
+	G_DEBUG=gc-friendly \
+	G_SLICE=always-malloc \
+	srcdir=$(srcdir) \
+	$(ENV)
+
+
+# check-tool: Run tests under $(TOOL)
+check-tool:
+	$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) check \
+	TESTS_ENVIRONMENT='$(TESTS_ENVIRONMENT) $(top_builddir)/libtool --mode=execute \
+	env $(TOOL)'
+# check-tool-raw: Run tests under $(TOOL), but don't run under libtool
+check-tool-raw:
+	$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) check \
+	TESTS_ENVIRONMENT='$(TESTS_ENVIRONMENT) \
+	env $(TOOL)'
+
+# check-gtester: Run tests under gtester
+GTESTER = gtester
+check-gtester:
+	$(AM_V_at)$(MAKE) $(AM_MAKEFLGS) check-tool-raw TOOL="$(GTESTER) --verbose --keep-going"
+
+
+# Check tests under valgrind.  Saves log to log-valgrind.txt
+VALGRIND_FLAGS = \
+	--tool=memcheck --suppressions=$(srcdir)/.valgrind-suppressions \
+	--track-origins=yes \
+	--leak-check=yes
+	$(EXTRA_VALGRIND_FLAGS)
+#	Can't do for now: --show-reachable=yes
+CLEANFILES +=  log-valgrind.txt
+valgrind_verbose = $(valgrind_verbose_$(V))
+valgrind_verbose_ = $(valgrind_verbose_$(AM_DEFAULT_VERBOSITY))
+valgrind_verbose_0 = | \
+	grep '\(^[^=]\|ERROR SUMMARY\|definitely lost\|indirectly lost\)' | grep -v ': 0'
+# TODO: The following check does not fail if valgrind finds error.  It should.
+check-valgrind:
+	$(AM_V_at)$(MAKE) $(AM_MAKEFLGS) check-tool TOOL="valgrind $(VALGRIND_FLAGS)" \
+	2>&1 | tee log-valgrind.txt $(valgrind_verbose)
+
+
+# check-symbols: Finds untested API symbols
+symbols-tested.txt: $(TEST_PROGS)
+	$(AM_V_GEN)$(top_builddir)/libtool --mode=execute nm $^ \
+	| grep ' U hb_' | sed 's/.* U hb_/hb_/' \
+	| sort | uniq > $@.tmp && mv $@.tmp $@
+symbols-exported.txt: $(top_builddir)/src/.libs/libharfbuzz.so
+	$(AM_V_GEN)$(top_builddir)/libtool --mode=execute nm $^ \
+	| grep ' T ' | sed 's/.* T //' | grep -v '^\(_init\|_fini\)$$' \
+	| sort | uniq > $@.tmp && mv $@.tmp $@
+symbols-untested.txt: symbols-tested.txt symbols-exported.txt
+	$(AM_V_GEN)diff $^ > $@.tmp; mv $@.tmp $@
+CLEANFILES += symbols-tested.txt symbols-exported.txt symbols-untested.txt
+check-symbols: symbols-untested.txt
+	@! cat $^ | grep .
+
+
+
+else
+check-am: err-glib
+err-glib:
+	@echo "You need to have glib support enabled to run the tests"
+	@exit 77
+endif
+
+.PHONY: check-symbols check-tool check-valgrind
+
+-include $(top_srcdir)/git.mk
diff --git a/test/api/hb-test.h b/test/api/hb-test.h
new file mode 100644
index 0000000..f36d53b
--- /dev/null
+++ b/test/api/hb-test.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#ifndef HB_TEST_H
+#define HB_TEST_H
+
+#include <config.h>
+
+#include <hb-glib.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+HB_BEGIN_DECLS
+
+/* Just in case */
+#undef G_DISABLE_ASSERT
+
+
+/* Misc */
+
+/* This is too ugly to be public API, but quite handy. */
+#define HB_TAG_CHAR4(s)   (HB_TAG(((const char *) s)[0], \
+				  ((const char *) s)[1], \
+				  ((const char *) s)[2], \
+				  ((const char *) s)[3]))
+
+
+static inline const char *
+srcdir (void)
+{
+  static const char *s;
+
+  if (!s) {
+    s = getenv ("srcdir");
+
+#ifdef SRCDIR
+    if (!s || !s[0])
+      s = SRCDIR;
+#endif
+
+    if (!s || !s[0])
+      s = ".";
+  }
+
+  return s;
+}
+
+
+/* Helpers */
+
+static inline void
+hb_test_init (int *argc, char ***argv)
+{
+  g_thread_init (NULL);
+  g_test_init (argc, argv, NULL);
+}
+
+static inline int
+hb_test_run (void)
+{
+  return g_test_run ();
+}
+
+
+/* Bugzilla helpers */
+
+static inline void
+hb_test_bug (const char *uri_base, unsigned int number)
+{
+  char *s = g_strdup_printf ("%u", number);
+
+  g_test_bug_base (uri_base);
+  g_test_bug (s);
+
+  g_free (s);
+}
+
+static inline void
+hb_test_bug_freedesktop (unsigned int number)
+{
+  hb_test_bug ("http://bugs.freedesktop.org/", number);
+}
+
+static inline void
+hb_test_bug_gnome (unsigned int number)
+{
+  hb_test_bug ("http://bugzilla.gnome.org/", number);
+}
+
+static inline void
+hb_test_bug_mozilla (unsigned int number)
+{
+  hb_test_bug ("http://bugzilla.mozilla.org/", number);
+}
+
+static inline void
+hb_test_bug_redhat (unsigned int number)
+{
+  hb_test_bug ("http://bugzilla.redhat.com/", number);
+}
+
+
+/* Wrap glib test functions to simplify.  Should have been in glib already. */
+
+/* Drops the "test_" prefix and converts '_' to '/'.
+ * Essentially builds test path from function name. */
+static inline char *
+hb_test_normalize_path (const char *path)
+{
+  char *s, *p;
+
+  g_assert (0 == strncmp (path, "test_", 5));
+  path += 4;
+
+  s = g_strdup (path);
+  for (p = s; *p; p++)
+    if (*p == '_')
+      *p = '/';
+
+  return s;
+}
+
+
+#if GLIB_CHECK_VERSION(2,25,12)
+typedef GTestFunc        hb_test_func_t;
+typedef GTestDataFunc    hb_test_data_func_t;
+typedef GTestFixtureFunc hb_test_fixture_func_t;
+#else
+typedef void (*hb_test_func_t)         (void);
+typedef void (*hb_test_data_func_t)    (gconstpointer user_data);
+typedef void (*hb_test_fixture_func_t) (void);
+#endif
+
+#if !GLIB_CHECK_VERSION(2,30,0)
+#define g_test_fail() g_error("Test failed")
+#endif
+
+static inline void
+hb_test_add_func (const char *test_path,
+		  hb_test_func_t   test_func)
+{
+  char *normal_path = hb_test_normalize_path (test_path);
+  g_test_add_func (normal_path, test_func);
+  g_free (normal_path);
+}
+#define hb_test_add(Func) hb_test_add_func (#Func, Func)
+
+static inline void
+hb_test_add_func_flavor (const char *test_path,
+			 const char *flavor,
+			 hb_test_func_t   test_func)
+{
+  char *path = g_strdup_printf ("%s/%s", test_path, flavor);
+  hb_test_add_func (path, test_func);
+  g_free (path);
+}
+#define hb_test_add_flavor(Flavor, Func) hb_test_add_func (#Func, Flavor, Func)
+
+static inline void
+hb_test_add_data_func (const char          *test_path,
+		       gconstpointer        test_data,
+		       hb_test_data_func_t  test_func)
+{
+  char *normal_path = hb_test_normalize_path (test_path);
+  g_test_add_data_func (normal_path, test_data, test_func);
+  g_free (normal_path);
+}
+#define hb_test_add_data(UserData, Func) hb_test_add_data_func (#Func, UserData, Func)
+
+static inline void
+hb_test_add_data_func_flavor (const char          *test_path,
+			      const char          *flavor,
+			      gconstpointer        test_data,
+			      hb_test_data_func_t  test_func)
+{
+  char *path = g_strdup_printf ("%s/%s", test_path, flavor);
+  hb_test_add_data_func (path, test_data, test_func);
+  g_free (path);
+}
+#define hb_test_add_data_flavor(UserData, Flavor, Func) hb_test_add_data_func_flavor (#Func, Flavor, UserData, Func)
+
+
+static inline void
+hb_test_add_vtable (const char             *test_path,
+		    gsize                   data_size,
+		    gconstpointer           test_data,
+		    hb_test_fixture_func_t  data_setup,
+		    hb_test_fixture_func_t  data_test,
+		    hb_test_fixture_func_t  data_teardown)
+{
+  char *normal_path = hb_test_normalize_path (test_path);
+  g_test_add_vtable (normal_path, data_size, test_data, data_setup, data_test, data_teardown);
+  g_free (normal_path);
+}
+#define hb_test_add_fixture(FixturePrefix, UserData, Func) \
+G_STMT_START { \
+  typedef G_PASTE (FixturePrefix, _t) Fixture; \
+  void (*add_vtable) (const char*, gsize, gconstpointer, \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer)) \
+	= (void (*) (const gchar *, gsize, gconstpointer, \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer))) hb_test_add_vtable; \
+  add_vtable (#Func, sizeof (G_PASTE (FixturePrefix, _t)), UserData, \
+	      G_PASTE (FixturePrefix, _init), Func, G_PASTE (FixturePrefix, _finish)); \
+} G_STMT_END
+
+static inline void
+hb_test_add_vtable_flavor (const char             *test_path,
+			   const char             *flavor,
+			   gsize                   data_size,
+			   gconstpointer           test_data,
+			   hb_test_fixture_func_t  data_setup,
+			   hb_test_fixture_func_t  data_test,
+			   hb_test_fixture_func_t  data_teardown)
+{
+  char *path = g_strdup_printf ("%s/%s", test_path, flavor);
+  hb_test_add_vtable (path, data_size, test_data, data_setup, data_test, data_teardown);
+  g_free (path);
+}
+#define hb_test_add_fixture_flavor(FixturePrefix, UserData, Flavor, Func) \
+G_STMT_START { \
+  typedef G_PASTE (FixturePrefix, _t) Fixture; \
+  void (*add_vtable) (const char*, const char *, gsize, gconstpointer, \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer), \
+		      void (*) (Fixture*, gconstpointer)) \
+	= (void (*) (const gchar *, const char *, gsize, gconstpointer, \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer), \
+		     void (*) (Fixture*, gconstpointer))) hb_test_add_vtable_flavor; \
+  add_vtable (#Func, Flavor, sizeof (G_PASTE (FixturePrefix, _t)), UserData, \
+	      G_PASTE (FixturePrefix, _init), Func, G_PASTE (FixturePrefix, _finish)); \
+} G_STMT_END
+
+
+HB_END_DECLS
+
+#endif /* HB_TEST_H */
diff --git a/test/api/test-blob.c b/test/api/test-blob.c
new file mode 100644
index 0000000..5fcb208
--- /dev/null
+++ b/test/api/test-blob.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-blob.h */
+
+#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MPROTECT) && defined(HAVE_MMAP)
+
+# define TEST_MMAP 1
+
+#ifdef HAVE_SYS_MMAN_H
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <sys/mman.h>
+#endif /* HAVE_SYS_MMAN_H */
+
+#endif
+
+
+static void
+test_blob_empty (void)
+{
+  hb_blob_t *blob;
+  unsigned int len;
+  const char *data;
+  char *data_writable;
+
+  g_assert (hb_blob_is_immutable (hb_blob_get_empty ()));
+  g_assert (hb_blob_get_empty () != NULL);
+  g_assert (hb_blob_get_empty () == hb_blob_create (NULL, 0, HB_MEMORY_MODE_READONLY, NULL, NULL));
+
+  blob = hb_blob_get_empty ();
+  g_assert (blob == hb_blob_get_empty ());
+
+  len = hb_blob_get_length (blob);
+  g_assert_cmpint (len, ==, 0);
+
+  data = hb_blob_get_data (blob, NULL);
+  g_assert (data == NULL);
+
+  data = hb_blob_get_data (blob, &len);
+  g_assert (data == NULL);
+  g_assert_cmpint (len, ==, 0);
+
+  data_writable = hb_blob_get_data_writable (blob, NULL);
+  g_assert (data == NULL);
+
+  data_writable = hb_blob_get_data_writable (blob, &len);
+  g_assert (data_writable == NULL);
+  g_assert_cmpint (len, ==, 0);
+}
+
+static const char test_data[] = "test\0data";
+
+static const char *blob_names[] = {
+  "duplicate",
+  "readonly",
+  "writable"
+#ifdef TEST_MMAP
+   , "readonly-may-make-writable"
+#endif
+};
+
+typedef struct
+{
+  hb_blob_t *blob;
+  int freed;
+  char *data;
+  unsigned int len;
+} fixture_t;
+
+static void
+free_up (fixture_t *fixture)
+{
+  g_assert_cmpint (fixture->freed, ==, 0);
+  fixture->freed++;
+}
+
+static void
+free_up_free (fixture_t *fixture)
+{
+  free_up (fixture);
+  free (fixture->data);
+}
+
+
+#ifdef TEST_MMAP
+static uintptr_t
+get_pagesize (void)
+{
+  uintptr_t pagesize = -1;
+
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE)
+  pagesize = (uintptr_t) sysconf (_SC_PAGE_SIZE);
+#elif defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+  pagesize = (uintptr_t) sysconf (_SC_PAGESIZE);
+#elif defined(HAVE_GETPAGESIZE)
+  pagesize = (uintptr_t) getpagesize ();
+#endif
+
+  g_assert (pagesize != (uintptr_t) -1);
+
+  return pagesize;
+}
+
+static void
+free_up_munmap (fixture_t *fixture)
+{
+  free_up (fixture);
+  munmap (fixture->data, get_pagesize ());
+}
+#endif
+
+#include <errno.h>
+static void
+fixture_init (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_memory_mode_t mm = (hb_memory_mode_t) GPOINTER_TO_INT (user_data);
+  unsigned int len;
+  const char *data;
+  hb_destroy_func_t free_func;
+
+  switch (GPOINTER_TO_INT (user_data))
+  {
+    case HB_MEMORY_MODE_DUPLICATE:
+      data = test_data;
+      len = sizeof (test_data);
+      free_func = (hb_destroy_func_t) free_up;
+      break;
+
+    case HB_MEMORY_MODE_READONLY:
+      data = test_data;
+      len = sizeof (test_data);
+      free_func = (hb_destroy_func_t) free_up;
+      break;
+
+    case HB_MEMORY_MODE_WRITABLE:
+      data = malloc (sizeof (test_data));
+      memcpy ((char *) data, test_data, sizeof (test_data));
+      len = sizeof (test_data);
+      free_func = (hb_destroy_func_t) free_up_free;
+      break;
+
+#ifdef TEST_MMAP
+    case HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE:
+    {
+      uintptr_t pagesize = get_pagesize ();
+
+      data = mmap (NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+      g_assert (data != (char *) -1);
+      memcpy ((char *) data, test_data, sizeof (test_data));
+      mprotect ((char *) data, pagesize, PROT_READ);
+      len = sizeof (test_data);
+      free_func = (hb_destroy_func_t) free_up_munmap;
+      break;
+    }
+#endif
+
+    default:
+      g_assert_not_reached ();
+  }
+
+  fixture->freed = 0;
+  fixture->data = (char *) data;
+  fixture->len = len;
+  fixture->blob = hb_blob_create (data, len, mm, fixture, free_func);
+}
+
+static void
+fixture_finish (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_blob_destroy (fixture->blob);
+  g_assert_cmpint (fixture->freed, ==, 1);
+}
+
+
+static void
+test_blob (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_blob_t *b = fixture->blob;
+  hb_memory_mode_t mm = GPOINTER_TO_INT (user_data);
+  unsigned int len;
+  const char *data;
+  char *data_writable;
+  unsigned int i;
+
+  g_assert (b);
+
+  len = hb_blob_get_length (b);
+  g_assert_cmpint (len, ==, fixture->len);
+
+  data = hb_blob_get_data (b, &len);
+  g_assert_cmpint (len, ==, fixture->len);
+  if (mm == HB_MEMORY_MODE_DUPLICATE) {
+    g_assert (data != fixture->data);
+    g_assert_cmpint (fixture->freed, ==, 1);
+    mm = HB_MEMORY_MODE_WRITABLE;
+  } else {
+    g_assert (data == fixture->data);
+    g_assert_cmpint (fixture->freed, ==, 0);
+  }
+
+  data_writable = hb_blob_get_data_writable (b, &len);
+  g_assert_cmpint (len, ==, fixture->len);
+  g_assert (data_writable);
+  g_assert (0 == memcmp (data_writable, fixture->data, fixture->len));
+  if (mm == HB_MEMORY_MODE_READONLY) {
+    g_assert (data_writable != data);
+    g_assert_cmpint (fixture->freed, ==, 1);
+  } else {
+    g_assert (data_writable == data);
+  }
+
+  data = hb_blob_get_data (b, &len);
+  g_assert_cmpint (len, ==, fixture->len);
+  g_assert (data == data_writable);
+
+  memset (data_writable, 0, fixture->len);
+
+  /* Now, make it immutable and watch get_data_writable() fail */
+
+  g_assert (!hb_blob_is_immutable (b));
+  hb_blob_make_immutable (b);
+  g_assert (hb_blob_is_immutable (b));
+
+  data_writable = hb_blob_get_data_writable (b, &len);
+  g_assert (!data_writable);
+  g_assert_cmpint (len, ==, 0);
+
+  data = hb_blob_get_data (b, &len);
+  g_assert_cmpint (len, ==, fixture->len);
+  for (i = 0; i < len; i++)
+    g_assert ('\0' == data[i]);
+}
+
+static void
+test_blob_subblob (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_blob_t *b = fixture->blob;
+
+  fixture->len -= 2;
+  fixture->data++;
+  fixture->blob = hb_blob_create_sub_blob (b, 1, fixture->len);
+  hb_blob_destroy (b);
+
+  test_blob (fixture, user_data);
+
+  fixture->data--;
+  fixture->len += 2;
+}
+
+
+int
+main (int argc, char **argv)
+{
+  unsigned int i;
+
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_blob_empty);
+
+  for (i = 0; i < G_N_ELEMENTS (blob_names); i++)
+  {
+    const void *blob_type = GINT_TO_POINTER (i);
+    const char *blob_name = blob_names[i];
+
+    hb_test_add_fixture_flavor (fixture, blob_type, blob_name, test_blob);
+    hb_test_add_fixture_flavor (fixture, blob_type, blob_name, test_blob_subblob);
+  }
+
+  /*
+   * create_sub_blob
+   */
+
+  return hb_test_run ();
+}
diff --git a/test/api/test-buffer.c b/test/api/test-buffer.c
new file mode 100644
index 0000000..ab818d0
--- /dev/null
+++ b/test/api/test-buffer.c
@@ -0,0 +1,783 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-buffer.h */
+
+
+static const char utf8[10] = "ab\360\240\200\200defg";
+static const uint16_t utf16[8] = {'a', 'b', 0xD840, 0xDC00, 'd', 'e', 'f', 'g'};
+static const uint32_t utf32[7] = {'a', 'b', 0x20000, 'd', 'e', 'f', 'g'};
+
+
+typedef enum {
+  BUFFER_EMPTY,
+  BUFFER_ONE_BY_ONE,
+  BUFFER_UTF32,
+  BUFFER_UTF16,
+  BUFFER_UTF8,
+  BUFFER_NUM_TYPES,
+} buffer_type_t;
+
+static const char *buffer_names[] = {
+  "empty",
+  "one-by-one",
+  "utf32",
+  "utf16",
+  "utf8"
+};
+
+typedef struct
+{
+  hb_buffer_t *buffer;
+} fixture_t;
+
+static void
+fixture_init (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_buffer_t *b;
+  unsigned int i;
+
+  b = fixture->buffer = hb_buffer_create ();
+
+  switch (GPOINTER_TO_INT (user_data))
+  {
+    case BUFFER_EMPTY:
+      break;
+
+    case BUFFER_ONE_BY_ONE:
+      for (i = 1; i < G_N_ELEMENTS (utf32) - 1; i++)
+	hb_buffer_add (b, utf32[i], 1, i);
+      break;
+
+    case BUFFER_UTF32:
+      hb_buffer_add_utf32 (b, utf32, G_N_ELEMENTS (utf32), 1, G_N_ELEMENTS (utf32) - 2);
+      break;
+
+    case BUFFER_UTF16:
+      hb_buffer_add_utf16 (b, utf16, G_N_ELEMENTS (utf16), 1, G_N_ELEMENTS (utf16) - 2);
+      break;
+
+    case BUFFER_UTF8:
+      hb_buffer_add_utf8  (b, utf8,  G_N_ELEMENTS (utf8),  1, G_N_ELEMENTS (utf8)  - 2);
+      break;
+
+    default:
+      g_assert_not_reached ();
+  }
+}
+
+static void
+fixture_finish (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_buffer_destroy (fixture->buffer);
+}
+
+
+static void
+test_buffer_properties (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_buffer_t *b = fixture->buffer;
+  hb_unicode_funcs_t *ufuncs;
+
+  /* test default properties */
+
+  g_assert (hb_buffer_get_unicode_funcs (b) == hb_unicode_funcs_get_default ());
+  g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_INVALID);
+  g_assert (hb_buffer_get_script (b) == HB_SCRIPT_INVALID);
+  g_assert (hb_buffer_get_language (b) == NULL);
+
+
+  /* test property changes are retained */
+  ufuncs = hb_unicode_funcs_create (NULL);
+  hb_buffer_set_unicode_funcs (b, ufuncs);
+  hb_unicode_funcs_destroy (ufuncs);
+  g_assert (hb_buffer_get_unicode_funcs (b) == ufuncs);
+
+  hb_buffer_set_direction (b, HB_DIRECTION_RTL);
+  g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_RTL);
+
+  hb_buffer_set_script (b, HB_SCRIPT_ARABIC);
+  g_assert (hb_buffer_get_script (b) == HB_SCRIPT_ARABIC);
+
+  hb_buffer_set_language (b, hb_language_from_string ("fa", -1));
+  g_assert (hb_buffer_get_language (b) == hb_language_from_string ("Fa", -1));
+
+
+  /* test reset clears properties */
+
+  hb_buffer_reset (b);
+
+  g_assert (hb_buffer_get_unicode_funcs (b) == hb_unicode_funcs_get_default ());
+  g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_INVALID);
+  g_assert (hb_buffer_get_script (b) == HB_SCRIPT_INVALID);
+  g_assert (hb_buffer_get_language (b) == NULL);
+}
+
+static void
+test_buffer_contents (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_buffer_t *b = fixture->buffer;
+  unsigned int i, len, len2;
+  buffer_type_t buffer_type = GPOINTER_TO_INT (user_data);
+  hb_glyph_info_t *glyphs;
+
+  if (buffer_type == BUFFER_EMPTY) {
+    g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+    return;
+  }
+
+  len = hb_buffer_get_length (b);
+  glyphs = hb_buffer_get_glyph_infos (b, NULL); /* test NULL */
+  glyphs = hb_buffer_get_glyph_infos (b, &len2);
+  g_assert_cmpint (len, ==, len2);
+  g_assert_cmpint (len, ==, 5);
+
+  for (i = 0; i < len; i++) {
+    g_assert_cmphex (glyphs[i].mask,      ==, 1);
+    g_assert_cmphex (glyphs[i].var1.u32,  ==, 0);
+    g_assert_cmphex (glyphs[i].var2.u32,  ==, 0);
+  }
+
+  for (i = 0; i < len; i++) {
+    unsigned int cluster;
+    cluster = 1+i;
+    if (i >= 2) {
+      if (buffer_type == BUFFER_UTF16)
+	cluster++;
+      else if (buffer_type == BUFFER_UTF8)
+        cluster += 3;
+    }
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+    g_assert_cmphex (glyphs[i].cluster,   ==, cluster);
+  }
+
+  /* reverse, test, and reverse back */
+
+  hb_buffer_reverse (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[len-i]);
+
+  hb_buffer_reverse (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+  /* reverse_clusters works same as reverse for now since each codepoint is
+   * in its own cluster */
+
+  hb_buffer_reverse_clusters (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[len-i]);
+
+  hb_buffer_reverse_clusters (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+  /* now form a cluster and test again */
+  glyphs[2].cluster = glyphs[1].cluster;
+
+  /* reverse, test, and reverse back */
+
+  hb_buffer_reverse (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[len-i]);
+
+  hb_buffer_reverse (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+  /* reverse_clusters twice still should return the original string,
+   * but when applied once, the 1-2 cluster should be retained. */
+
+  hb_buffer_reverse_clusters (b);
+  for (i = 0; i < len; i++) {
+    unsigned int j = len-1-i;
+    if (j == 1)
+      j = 2;
+    else if (j == 2)
+      j = 1;
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+j]);
+  }
+
+  hb_buffer_reverse_clusters (b);
+  for (i = 0; i < len; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+
+  /* test setting length */
+
+  /* enlarge */
+  g_assert (hb_buffer_set_length (b, 10));
+  glyphs = hb_buffer_get_glyph_infos (b, NULL);
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 10);
+  for (i = 0; i < 5; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+  for (i = 5; i < 10; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, 0);
+  /* shrink */
+  g_assert (hb_buffer_set_length (b, 3));
+  glyphs = hb_buffer_get_glyph_infos (b, NULL);
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 3);
+  for (i = 0; i < 3; i++)
+    g_assert_cmphex (glyphs[i].codepoint, ==, utf32[1+i]);
+
+
+  g_assert (hb_buffer_allocation_successful (b));
+
+
+  /* test reset clears content */
+
+  hb_buffer_reset (b);
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+}
+
+static void
+test_buffer_positions (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_buffer_t *b = fixture->buffer;
+  unsigned int i, len, len2;
+  hb_glyph_position_t *positions;
+
+  /* Without shaping, positions should all be zero */
+  len = hb_buffer_get_length (b);
+  positions = hb_buffer_get_glyph_positions (b, NULL); /* test NULL */
+  positions = hb_buffer_get_glyph_positions (b, &len2);
+  g_assert_cmpint (len, ==, len2);
+  for (i = 0; i < len; i++) {
+    g_assert_cmpint (0, ==, positions[i].x_advance);
+    g_assert_cmpint (0, ==, positions[i].y_advance);
+    g_assert_cmpint (0, ==, positions[i].x_offset);
+    g_assert_cmpint (0, ==, positions[i].y_offset);
+    g_assert_cmpint (0, ==, positions[i].var.i32);
+  }
+
+  /* test reset clears content */
+  hb_buffer_reset (b);
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+}
+
+static void
+test_buffer_allocation (fixture_t *fixture, gconstpointer user_data)
+{
+  hb_buffer_t *b = fixture->buffer;
+
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+
+  g_assert (hb_buffer_pre_allocate (b, 100));
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+  g_assert (hb_buffer_allocation_successful (b));
+
+  /* lets try a huge allocation, make sure it fails */
+  g_assert (!hb_buffer_pre_allocate (b, (unsigned int) -1));
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+  g_assert (!hb_buffer_allocation_successful (b));
+
+  /* small one again */
+  g_assert (hb_buffer_pre_allocate (b, 50));
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+  g_assert (!hb_buffer_allocation_successful (b));
+
+  hb_buffer_reset (b);
+  g_assert (hb_buffer_allocation_successful (b));
+
+  /* all allocation and size  */
+  g_assert (!hb_buffer_pre_allocate (b, ((unsigned int) -1) / 20 + 1));
+  g_assert (!hb_buffer_allocation_successful (b));
+
+  hb_buffer_reset (b);
+  g_assert (hb_buffer_allocation_successful (b));
+
+  /* technically, this one can actually pass on 64bit machines, but
+   * I'm doubtful that any malloc allows 4GB allocations at a time.
+   * But let's only enable it on a 32-bit machine. */
+  if (sizeof (long) == 4) {
+    g_assert (!hb_buffer_pre_allocate (b, ((unsigned int) -1) / 20 - 1));
+    g_assert (!hb_buffer_allocation_successful (b));
+  }
+
+  hb_buffer_reset (b);
+  g_assert (hb_buffer_allocation_successful (b));
+}
+
+
+typedef struct {
+  const char utf8[8];
+  const uint32_t codepoints[8];
+} utf8_conversion_test_t;
+
+/* note: we skip the first and last byte when adding to buffer */
+static const utf8_conversion_test_t utf8_conversion_tests[] = {
+  {"a\303\207", {-1}},
+  {"a\303\207b", {0xC7}},
+  {"ab\303cd", {'b', -1, 'c'}},
+  {"ab\303\302\301cd", {'b', -1, -1, -1, 'c'}}
+};
+
+static void
+test_buffer_utf8_conversion (void)
+{
+  hb_buffer_t *b;
+  hb_glyph_info_t *glyphs;
+  unsigned int bytes, chars, i, j, len;
+
+  b = hb_buffer_create ();
+
+  for (i = 0; i < G_N_ELEMENTS (utf8_conversion_tests); i++)
+  {
+    const utf8_conversion_test_t *test = &utf8_conversion_tests[i];
+    char *escaped;
+
+    escaped = g_strescape (test->utf8, NULL);
+    g_test_message ("UTF-8 test #%d: %s", i, escaped);
+    g_free (escaped);
+
+    bytes = strlen (test->utf8);
+    for (chars = 0; test->codepoints[chars]; chars++)
+      ;
+
+    hb_buffer_reset (b);
+    hb_buffer_add_utf8 (b, test->utf8, bytes,  1, bytes - 2);
+
+    glyphs = hb_buffer_get_glyph_infos (b, &len);
+    g_assert_cmpint (len, ==, chars);
+    for (j = 0; j < chars; j++)
+      g_assert_cmphex (glyphs[j].codepoint, ==, test->codepoints[j]);
+  }
+
+  hb_buffer_destroy (b);
+}
+
+
+
+/* Following test table is adapted from glib/glib/tests/utf8-validate.c
+ * with relicensing permission from Matthias Clasen. */
+
+typedef struct {
+  const char *utf8;
+  int max_len;
+  unsigned int offset;
+  gboolean valid;
+} utf8_validity_test_t;
+
+static const utf8_validity_test_t utf8_validity_tests[] = {
+  /* some tests to check max_len handling */
+  /* length 1 */
+  { "abcde", -1, 5, TRUE },
+  { "abcde", 3, 3, TRUE },
+  { "abcde", 5, 5, TRUE },
+  /* length 2 */
+  { "\xc2\xa9\xc2\xa9\xc2\xa9", -1, 6, TRUE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  1, 0, FALSE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  2, 2, TRUE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  3, 2, FALSE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  4, 4, TRUE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  5, 4, FALSE },
+  { "\xc2\xa9\xc2\xa9\xc2\xa9",  6, 6, TRUE },
+  /* length 3 */
+  { "\xe2\x89\xa0\xe2\x89\xa0", -1, 6, TRUE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  1, 0, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  2, 0, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  3, 3, TRUE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  4, 3, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  5, 3, FALSE },
+  { "\xe2\x89\xa0\xe2\x89\xa0",  6, 6, TRUE },
+
+  /* examples from http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt */
+  /* greek 'kosme' */
+  { "\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5", -1, 11, TRUE },
+  /* first sequence of each length */
+  { "\x00", -1, 0, TRUE },
+  { "\xc2\x80", -1, 2, TRUE },
+  { "\xe0\xa0\x80", -1, 3, TRUE },
+  { "\xf0\x90\x80\x80", -1, 4, TRUE },
+  { "\xf8\x88\x80\x80\x80", -1, 0, FALSE },
+  { "\xfc\x84\x80\x80\x80\x80", -1, 0, FALSE },
+  /* last sequence of each length */
+  { "\x7f", -1, 1, TRUE },
+  { "\xdf\xbf", -1, 2, TRUE },
+  { "\xef\xbf\xbf", -1, 0, TRUE },
+  { "\xf7\xbf\xbf\xbf", -1, 0, TRUE },
+  { "\xfb\xbf\xbf\xbf\xbf", -1, 0, FALSE },
+  { "\xfd\xbf\xbf\xbf\xbf\xbf", -1, 0, FALSE },
+  /* other boundary conditions */
+  { "\xed\x9f\xbf", -1, 3, TRUE },
+  { "\xee\x80\x80", -1, 3, TRUE },
+  { "\xef\xbf\xbd", -1, 3, TRUE },
+  { "\xf4\x8f\xbf\xbf", -1, 0, TRUE },
+  /* malformed sequences */
+  /* continuation bytes */
+  { "\x80", -1, 0, FALSE },
+  { "\xbf", -1, 0, FALSE },
+  { "\x80\xbf", -1, 0, FALSE },
+  { "\x80\xbf\x80", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf\x80", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf\x80\xbf", -1, 0, FALSE },
+  { "\x80\xbf\x80\xbf\x80\xbf\x80", -1, 0, FALSE },
+
+  /* all possible continuation byte */
+  { "\x80", -1, 0, FALSE },
+  { "\x81", -1, 0, FALSE },
+  { "\x82", -1, 0, FALSE },
+  { "\x83", -1, 0, FALSE },
+  { "\x84", -1, 0, FALSE },
+  { "\x85", -1, 0, FALSE },
+  { "\x86", -1, 0, FALSE },
+  { "\x87", -1, 0, FALSE },
+  { "\x88", -1, 0, FALSE },
+  { "\x89", -1, 0, FALSE },
+  { "\x8a", -1, 0, FALSE },
+  { "\x8b", -1, 0, FALSE },
+  { "\x8c", -1, 0, FALSE },
+  { "\x8d", -1, 0, FALSE },
+  { "\x8e", -1, 0, FALSE },
+  { "\x8f", -1, 0, FALSE },
+  { "\x90", -1, 0, FALSE },
+  { "\x91", -1, 0, FALSE },
+  { "\x92", -1, 0, FALSE },
+  { "\x93", -1, 0, FALSE },
+  { "\x94", -1, 0, FALSE },
+  { "\x95", -1, 0, FALSE },
+  { "\x96", -1, 0, FALSE },
+  { "\x97", -1, 0, FALSE },
+  { "\x98", -1, 0, FALSE },
+  { "\x99", -1, 0, FALSE },
+  { "\x9a", -1, 0, FALSE },
+  { "\x9b", -1, 0, FALSE },
+  { "\x9c", -1, 0, FALSE },
+  { "\x9d", -1, 0, FALSE },
+  { "\x9e", -1, 0, FALSE },
+  { "\x9f", -1, 0, FALSE },
+  { "\xa0", -1, 0, FALSE },
+  { "\xa1", -1, 0, FALSE },
+  { "\xa2", -1, 0, FALSE },
+  { "\xa3", -1, 0, FALSE },
+  { "\xa4", -1, 0, FALSE },
+  { "\xa5", -1, 0, FALSE },
+  { "\xa6", -1, 0, FALSE },
+  { "\xa7", -1, 0, FALSE },
+  { "\xa8", -1, 0, FALSE },
+  { "\xa9", -1, 0, FALSE },
+  { "\xaa", -1, 0, FALSE },
+  { "\xab", -1, 0, FALSE },
+  { "\xac", -1, 0, FALSE },
+  { "\xad", -1, 0, FALSE },
+  { "\xae", -1, 0, FALSE },
+  { "\xaf", -1, 0, FALSE },
+  { "\xb0", -1, 0, FALSE },
+  { "\xb1", -1, 0, FALSE },
+  { "\xb2", -1, 0, FALSE },
+  { "\xb3", -1, 0, FALSE },
+  { "\xb4", -1, 0, FALSE },
+  { "\xb5", -1, 0, FALSE },
+  { "\xb6", -1, 0, FALSE },
+  { "\xb7", -1, 0, FALSE },
+  { "\xb8", -1, 0, FALSE },
+  { "\xb9", -1, 0, FALSE },
+  { "\xba", -1, 0, FALSE },
+  { "\xbb", -1, 0, FALSE },
+  { "\xbc", -1, 0, FALSE },
+  { "\xbd", -1, 0, FALSE },
+  { "\xbe", -1, 0, FALSE },
+  { "\xbf", -1, 0, FALSE },
+  /* lone start characters */
+  { "\xc0\x20", -1, 0, FALSE },
+  { "\xc1\x20", -1, 0, FALSE },
+  { "\xc2\x20", -1, 0, FALSE },
+  { "\xc3\x20", -1, 0, FALSE },
+  { "\xc4\x20", -1, 0, FALSE },
+  { "\xc5\x20", -1, 0, FALSE },
+  { "\xc6\x20", -1, 0, FALSE },
+  { "\xc7\x20", -1, 0, FALSE },
+  { "\xc8\x20", -1, 0, FALSE },
+  { "\xc9\x20", -1, 0, FALSE },
+  { "\xca\x20", -1, 0, FALSE },
+  { "\xcb\x20", -1, 0, FALSE },
+  { "\xcc\x20", -1, 0, FALSE },
+  { "\xcd\x20", -1, 0, FALSE },
+  { "\xce\x20", -1, 0, FALSE },
+  { "\xcf\x20", -1, 0, FALSE },
+  { "\xd0\x20", -1, 0, FALSE },
+  { "\xd1\x20", -1, 0, FALSE },
+  { "\xd2\x20", -1, 0, FALSE },
+  { "\xd3\x20", -1, 0, FALSE },
+  { "\xd4\x20", -1, 0, FALSE },
+  { "\xd5\x20", -1, 0, FALSE },
+  { "\xd6\x20", -1, 0, FALSE },
+  { "\xd7\x20", -1, 0, FALSE },
+  { "\xd8\x20", -1, 0, FALSE },
+  { "\xd9\x20", -1, 0, FALSE },
+  { "\xda\x20", -1, 0, FALSE },
+  { "\xdb\x20", -1, 0, FALSE },
+  { "\xdc\x20", -1, 0, FALSE },
+  { "\xdd\x20", -1, 0, FALSE },
+  { "\xde\x20", -1, 0, FALSE },
+  { "\xdf\x20", -1, 0, FALSE },
+  { "\xe0\x20", -1, 0, FALSE },
+  { "\xe1\x20", -1, 0, FALSE },
+  { "\xe2\x20", -1, 0, FALSE },
+  { "\xe3\x20", -1, 0, FALSE },
+  { "\xe4\x20", -1, 0, FALSE },
+  { "\xe5\x20", -1, 0, FALSE },
+  { "\xe6\x20", -1, 0, FALSE },
+  { "\xe7\x20", -1, 0, FALSE },
+  { "\xe8\x20", -1, 0, FALSE },
+  { "\xe9\x20", -1, 0, FALSE },
+  { "\xea\x20", -1, 0, FALSE },
+  { "\xeb\x20", -1, 0, FALSE },
+  { "\xec\x20", -1, 0, FALSE },
+  { "\xed\x20", -1, 0, FALSE },
+  { "\xee\x20", -1, 0, FALSE },
+  { "\xef\x20", -1, 0, FALSE },
+  { "\xf0\x20", -1, 0, FALSE },
+  { "\xf1\x20", -1, 0, FALSE },
+  { "\xf2\x20", -1, 0, FALSE },
+  { "\xf3\x20", -1, 0, FALSE },
+  { "\xf4\x20", -1, 0, FALSE },
+  { "\xf5\x20", -1, 0, FALSE },
+  { "\xf6\x20", -1, 0, FALSE },
+  { "\xf7\x20", -1, 0, FALSE },
+  { "\xf8\x20", -1, 0, FALSE },
+  { "\xf9\x20", -1, 0, FALSE },
+  { "\xfa\x20", -1, 0, FALSE },
+  { "\xfb\x20", -1, 0, FALSE },
+  { "\xfc\x20", -1, 0, FALSE },
+  { "\xfd\x20", -1, 0, FALSE },
+  /* missing continuation bytes */
+  { "\x20\xc0", -1, 1, FALSE },
+  { "\x20\xe0\x80", -1, 1, FALSE },
+  { "\x20\xf0\x80\x80", -1, 1, FALSE },
+  { "\x20\xf8\x80\x80\x80", -1, 1, FALSE },
+  { "\x20\xfc\x80\x80\x80\x80", -1, 1, FALSE },
+  { "\x20\xdf", -1, 1, FALSE },
+  { "\x20\xef\xbf", -1, 1, FALSE },
+  { "\x20\xf7\xbf\xbf", -1, 1, FALSE },
+  { "\x20\xfb\xbf\xbf\xbf", -1, 1, FALSE },
+  { "\x20\xfd\xbf\xbf\xbf\xbf", -1, 1, FALSE },
+  /* impossible bytes */
+  { "\x20\xfe\x20", -1, 1, FALSE },
+  { "\x20\xff\x20", -1, 1, FALSE },
+#if 0
+  /* XXX fix these, or document that we don't detect them? */
+  /* overlong sequences */
+  { "\x20\xc0\xaf\x20", -1, 1, FALSE },
+  { "\x20\xe0\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xf0\x80\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xf8\x80\x80\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xfc\x80\x80\x80\x80\xaf\x20", -1, 1, FALSE },
+  { "\x20\xc1\xbf\x20", -1, 1, FALSE },
+  { "\x20\xe0\x9f\xbf\x20", -1, 1, FALSE },
+  { "\x20\xf0\x8f\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xf8\x87\xbf\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xfc\x83\xbf\xbf\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xc0\x80\x20", -1, 1, FALSE },
+  { "\x20\xe0\x80\x80\x20", -1, 1, FALSE },
+  { "\x20\xf0\x80\x80\x80\x20", -1, 1, FALSE },
+  { "\x20\xf8\x80\x80\x80\x80\x20", -1, 1, FALSE },
+  { "\x20\xfc\x80\x80\x80\x80\x80\x20", -1, 1, FALSE },
+  /* illegal code positions */
+  { "\x20\xed\xa0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xad\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xae\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xaf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xbe\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xa0\x80\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xa0\x80\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xad\xbf\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xad\xbf\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xae\x80\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xae\x80\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xed\xaf\xbf\xed\xb0\x80\x20", -1, 1, FALSE },
+  { "\x20\xed\xaf\xbf\xed\xbf\xbf\x20", -1, 1, FALSE },
+  { "\x20\xef\xbf\xbe\x20", -1, 1, FALSE },
+  { "\x20\xef\xbf\xbf\x20", -1, 1, FALSE },
+#endif
+  { "", -1, 0, TRUE }
+};
+
+static void
+test_buffer_utf8_validity (void)
+{
+  hb_buffer_t *b;
+  unsigned int i;
+
+  b = hb_buffer_create ();
+
+  for (i = 0; i < G_N_ELEMENTS (utf8_validity_tests); i++)
+  {
+    const utf8_validity_test_t *test = &utf8_validity_tests[i];
+    unsigned int text_bytes, segment_bytes, j, len;
+    hb_glyph_info_t *glyphs;
+    char *escaped;
+
+    escaped = g_strescape (test->utf8, NULL);
+    g_test_message ("UTF-8 test #%d: %s", i, escaped);
+    g_free (escaped);
+
+    text_bytes = strlen (test->utf8);
+    if (test->max_len == -1)
+      segment_bytes = text_bytes;
+    else
+      segment_bytes = test->max_len;
+
+    hb_buffer_reset (b);
+    hb_buffer_add_utf8 (b, test->utf8, text_bytes,  0, segment_bytes);
+
+    glyphs = hb_buffer_get_glyph_infos (b, &len);
+    for (j = 0; j < len; j++)
+      if (glyphs[j].codepoint == (hb_codepoint_t) -1)
+	break;
+
+    g_assert (test->valid ? j == len : j < len);
+    if (!test->valid)
+      g_assert (glyphs[j].cluster == test->offset);
+  }
+
+  hb_buffer_destroy (b);
+}
+
+
+typedef struct {
+  const uint16_t utf16[8];
+  const uint32_t codepoints[8];
+} utf16_conversion_test_t;
+
+/* note: we skip the first and last item from utf16 when adding to buffer */
+static const utf16_conversion_test_t utf16_conversion_tests[] = {
+  {{0x41, 0x004D, 0x0430, 0x4E8C, 0xD800, 0xDF02, 0x61} , {0x004D, 0x0430, 0x4E8C, 0x10302}},
+  {{0x41, 0xD800, 0xDF02, 0x61}, {0x10302}},
+  {{0x41, 0xD800, 0xDF02}, {-1}},
+  {{0x41, 0x61, 0xD800, 0xDF02}, {0x61, -1}},
+  {{0x41, 0xD800, 0x61, 0xDF02}, {-1, 0x61}},
+  {{0x41, 0x61}, {}}
+};
+
+static void
+test_buffer_utf16_conversion (void)
+{
+  hb_buffer_t *b;
+  unsigned int i;
+
+  b = hb_buffer_create ();
+
+  for (i = 0; i < G_N_ELEMENTS (utf16_conversion_tests); i++)
+  {
+    const utf16_conversion_test_t *test = &utf16_conversion_tests[i];
+    unsigned int u_len, chars, j, len;
+    hb_glyph_info_t *glyphs;
+
+    g_test_message ("UTF-16 test #%d", i);
+
+    for (u_len = 0; test->utf16[u_len]; u_len++)
+      ;
+    for (chars = 0; test->codepoints[chars]; chars++)
+      ;
+
+    hb_buffer_reset (b);
+    hb_buffer_add_utf16 (b, test->utf16, u_len,  1, u_len - 2);
+
+    glyphs = hb_buffer_get_glyph_infos (b, &len);
+    g_assert_cmpint (len, ==, chars);
+    for (j = 0; j < chars; j++)
+      g_assert_cmphex (glyphs[j].codepoint, ==, test->codepoints[j]);
+  }
+
+  hb_buffer_destroy (b);
+}
+
+static void
+test_empty (hb_buffer_t *b)
+{
+  g_assert_cmpint (hb_buffer_get_length (b), ==, 0);
+  g_assert (!hb_buffer_get_glyph_infos (b, NULL));
+  g_assert (!hb_buffer_get_glyph_positions (b, NULL));
+}
+
+static void
+test_buffer_empty (void)
+{
+  hb_buffer_t *b = hb_buffer_get_empty ();
+
+  g_assert (hb_buffer_get_empty ());
+  g_assert (hb_buffer_get_empty () == b);
+
+  g_assert (!hb_buffer_allocation_successful (b));
+
+  test_empty (b);
+
+  hb_buffer_add_utf32 (b, utf32, G_N_ELEMENTS (utf32), 1, G_N_ELEMENTS (utf32) - 2);
+
+  test_empty (b);
+
+  hb_buffer_reverse (b);
+  hb_buffer_reverse_clusters (b);
+
+  g_assert (!hb_buffer_set_length (b, 10));
+
+  test_empty (b);
+
+  g_assert (hb_buffer_set_length (b, 0));
+
+  test_empty (b);
+
+  g_assert (!hb_buffer_allocation_successful (b));
+
+  hb_buffer_reset (b);
+
+  test_empty (b);
+
+  g_assert (!hb_buffer_allocation_successful (b));
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned int i;
+
+  hb_test_init (&argc, &argv);
+
+  for (i = 0; i < BUFFER_NUM_TYPES; i++)
+  {
+    const void *buffer_type = GINT_TO_POINTER (i);
+    const char *buffer_name = buffer_names[i];
+
+    hb_test_add_fixture_flavor (fixture, buffer_type, buffer_name, test_buffer_properties);
+    hb_test_add_fixture_flavor (fixture, buffer_type, buffer_name, test_buffer_contents);
+    hb_test_add_fixture_flavor (fixture, buffer_type, buffer_name, test_buffer_positions);
+  }
+
+  hb_test_add_fixture (fixture, GINT_TO_POINTER (BUFFER_EMPTY), test_buffer_allocation);
+
+  hb_test_add (test_buffer_utf8_conversion);
+  hb_test_add (test_buffer_utf8_validity);
+  hb_test_add (test_buffer_utf16_conversion);
+  hb_test_add (test_buffer_empty);
+
+  return hb_test_run();
+}
diff --git a/test/api/test-c.c b/test/api/test-c.c
new file mode 100644
index 0000000..25a38e5
--- /dev/null
+++ b/test/api/test-c.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+/* This file tests that all headers can be included from .c files */
+
+
+#include <config.h>
+
+#include <hb.h>
+
+#ifdef HAVE_GLIB
+#include <hb-glib.h>
+#endif
+
+#ifdef HAVE_ICU
+#include <hb-icu.h>
+#endif
+
+#ifdef HAVE_FREETYPE
+#include <hb-ft.h>
+#endif
+
+#ifdef HAVE_OT
+#include <hb-ot.h>
+#endif
+
+#ifdef HAVE_UNISCRIBE
+#include <hb-uniscribe.h>
+#endif
+
+int
+main (int argc, char **argv)
+{
+  return !*hb_shape_list_shapers ();
+}
diff --git a/test/api/test-common.c b/test/api/test-common.c
new file mode 100644
index 0000000..e00e601
--- /dev/null
+++ b/test/api/test-common.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-common.h */
+
+
+static void
+test_types_int (void)
+{
+  /* We already ASSERT_STATIC these in hb-private.h, but anyway */
+  g_assert_cmpint (sizeof (int8_t), ==, 1);
+  g_assert_cmpint (sizeof (uint8_t), ==, 1);
+  g_assert_cmpint (sizeof (int16_t), ==, 2);
+  g_assert_cmpint (sizeof (uint16_t), ==, 2);
+  g_assert_cmpint (sizeof (int32_t), ==, 4);
+  g_assert_cmpint (sizeof (uint32_t), ==, 4);
+  g_assert_cmpint (sizeof (int64_t), ==, 8);
+  g_assert_cmpint (sizeof (uint64_t), ==, 8);
+
+  g_assert_cmpint (sizeof (hb_codepoint_t), ==, 4);
+  g_assert_cmpint (sizeof (hb_position_t), ==, 4);
+  g_assert_cmpint (sizeof (hb_mask_t), ==, 4);
+  g_assert_cmpint (sizeof (hb_var_int_t), ==, 4);
+}
+
+static void
+test_types_direction (void)
+{
+  g_assert_cmpint ((signed) HB_DIRECTION_INVALID, ==, -1);
+  g_assert_cmpint (HB_DIRECTION_LTR, ==, 0);
+
+  g_assert (HB_DIRECTION_IS_HORIZONTAL (HB_DIRECTION_LTR));
+  g_assert (HB_DIRECTION_IS_HORIZONTAL (HB_DIRECTION_RTL));
+  g_assert (!HB_DIRECTION_IS_HORIZONTAL (HB_DIRECTION_TTB));
+  g_assert (!HB_DIRECTION_IS_HORIZONTAL (HB_DIRECTION_BTT));
+
+  g_assert (!HB_DIRECTION_IS_VERTICAL (HB_DIRECTION_LTR));
+  g_assert (!HB_DIRECTION_IS_VERTICAL (HB_DIRECTION_RTL));
+  g_assert (HB_DIRECTION_IS_VERTICAL (HB_DIRECTION_TTB));
+  g_assert (HB_DIRECTION_IS_VERTICAL (HB_DIRECTION_BTT));
+
+  g_assert (HB_DIRECTION_IS_FORWARD (HB_DIRECTION_LTR));
+  g_assert (HB_DIRECTION_IS_FORWARD (HB_DIRECTION_TTB));
+  g_assert (!HB_DIRECTION_IS_FORWARD (HB_DIRECTION_RTL));
+  g_assert (!HB_DIRECTION_IS_FORWARD (HB_DIRECTION_BTT));
+
+  g_assert (!HB_DIRECTION_IS_BACKWARD (HB_DIRECTION_LTR));
+  g_assert (!HB_DIRECTION_IS_BACKWARD (HB_DIRECTION_TTB));
+  g_assert (HB_DIRECTION_IS_BACKWARD (HB_DIRECTION_RTL));
+  g_assert (HB_DIRECTION_IS_BACKWARD (HB_DIRECTION_BTT));
+
+  g_assert_cmpint (HB_DIRECTION_REVERSE (HB_DIRECTION_LTR), ==, HB_DIRECTION_RTL);
+  g_assert_cmpint (HB_DIRECTION_REVERSE (HB_DIRECTION_RTL), ==, HB_DIRECTION_LTR);
+  g_assert_cmpint (HB_DIRECTION_REVERSE (HB_DIRECTION_TTB), ==, HB_DIRECTION_BTT);
+  g_assert_cmpint (HB_DIRECTION_REVERSE (HB_DIRECTION_BTT), ==, HB_DIRECTION_TTB);
+
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string (NULL, -1));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("", -1));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("t", 0));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("x", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("r", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("rtl", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("RtL", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("right-to-left", -1));
+  g_assert_cmpint (HB_DIRECTION_TTB, ==, hb_direction_from_string ("ttb", -1));
+
+  g_assert (0 == strcmp ("ltr", hb_direction_to_string (HB_DIRECTION_LTR)));
+  g_assert (0 == strcmp ("rtl", hb_direction_to_string (HB_DIRECTION_RTL)));
+  g_assert (0 == strcmp ("ttb", hb_direction_to_string (HB_DIRECTION_TTB)));
+  g_assert (0 == strcmp ("btt", hb_direction_to_string (HB_DIRECTION_BTT)));
+  g_assert (0 == strcmp ("invalid", hb_direction_to_string (HB_DIRECTION_INVALID)));
+}
+
+static void
+test_types_tag (void)
+{
+  g_assert_cmphex (HB_TAG_NONE, ==, 0);
+
+  g_assert_cmphex (HB_TAG ('a','B','c','D'), ==, 0x61426344);
+
+  g_assert_cmphex (hb_tag_from_string ("aBcDe", -1), ==, 0x61426344);
+  g_assert_cmphex (hb_tag_from_string ("aBcD", -1),  ==, 0x61426344);
+  g_assert_cmphex (hb_tag_from_string ("aBc", -1),   ==, 0x61426320);
+  g_assert_cmphex (hb_tag_from_string ("aB", -1),    ==, 0x61422020);
+  g_assert_cmphex (hb_tag_from_string ("a", -1),     ==, 0x61202020);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  1), ==, 0x61202020);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  2), ==, 0x61422020);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  3), ==, 0x61426320);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  4), ==, 0x61426344);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  4), ==, 0x61426344);
+
+  g_assert_cmphex (hb_tag_from_string ("", -1),      ==, HB_TAG_NONE);
+  g_assert_cmphex (hb_tag_from_string ("x", 0),      ==, HB_TAG_NONE);
+  g_assert_cmphex (hb_tag_from_string (NULL, -1),    ==, HB_TAG_NONE);
+}
+
+static void
+test_types_script (void)
+{
+  hb_tag_t arab = HB_TAG_CHAR4 ("arab");
+  hb_tag_t Arab = HB_TAG_CHAR4 ("Arab");
+  hb_tag_t ARAB = HB_TAG_CHAR4 ("ARAB");
+
+  hb_tag_t wWyZ = HB_TAG_CHAR4 ("wWyZ");
+  hb_tag_t Wwyz = HB_TAG_CHAR4 ("Wwyz");
+
+  hb_tag_t x123 = HB_TAG_CHAR4 ("x123");
+
+  g_assert_cmpint (HB_SCRIPT_INVALID, ==, (hb_script_t) HB_TAG_NONE);
+  g_assert_cmphex (HB_SCRIPT_ARABIC, !=, HB_SCRIPT_LATIN);
+
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string (NULL, -1));
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string ("", -1));
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string ("x", 0));
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_string ("x", -1));
+
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("arab", -1));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("Arab", -1));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("ARAB", -1));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("Arabic", 6));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, !=, hb_script_from_string ("Arabic", 3));
+
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_iso15924_tag (arab));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_iso15924_tag (Arab));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_iso15924_tag (ARAB));
+
+  /* Arbitrary tags that look like may be valid ISO 15924 should be preserved. */
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, !=, hb_script_from_string ("wWyZ", -1));
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, !=, hb_script_from_iso15924_tag (wWyZ));
+  /* Otherwise, UNKNOWN should be returned. */
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_string ("x123", -1));
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_iso15924_tag (x123));
+
+  g_assert_cmphex (hb_script_to_iso15924_tag (HB_SCRIPT_ARABIC), ==, Arab);
+  g_assert_cmphex (hb_script_to_iso15924_tag (hb_script_from_iso15924_tag (wWyZ)), ==, Wwyz);
+
+  g_assert_cmpint (hb_script_get_horizontal_direction (HB_SCRIPT_LATIN), ==, HB_DIRECTION_LTR);
+  g_assert_cmpint (hb_script_get_horizontal_direction (HB_SCRIPT_ARABIC), ==, HB_DIRECTION_RTL);
+  g_assert_cmpint (hb_script_get_horizontal_direction (hb_script_from_iso15924_tag (wWyZ)), ==, HB_DIRECTION_LTR);
+}
+
+static void
+test_types_language (void)
+{
+  hb_language_t fa = hb_language_from_string ("fa", -1);
+  hb_language_t fa_IR = hb_language_from_string ("fa_IR", -1);
+  hb_language_t fa_ir = hb_language_from_string ("fa-ir", -1);
+  hb_language_t en = hb_language_from_string ("en", -1);
+
+  g_assert (HB_LANGUAGE_INVALID == NULL);
+
+  g_assert (fa != NULL);
+  g_assert (fa_IR != NULL);
+  g_assert (fa_IR == fa_ir);
+
+  g_assert (en != NULL);
+  g_assert (en != fa);
+
+  /* Test recall */
+  g_assert (en == hb_language_from_string ("en", -1));
+  g_assert (en == hb_language_from_string ("eN", -1));
+  g_assert (en == hb_language_from_string ("Enx", 2));
+
+  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string (NULL, -1));
+  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string ("", -1));
+  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string ("en", 0));
+  g_assert (HB_LANGUAGE_INVALID != hb_language_from_string ("en", 1));
+  g_assert (NULL == hb_language_to_string (HB_LANGUAGE_INVALID));
+
+  /* Not sure how to test this better.  Setting env vars
+   * here doesn't sound like the right approach, and I'm
+   * not sure that it even works. */
+  g_assert (HB_LANGUAGE_INVALID != hb_language_get_default ());
+}
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_types_int);
+  hb_test_add (test_types_direction);
+  hb_test_add (test_types_tag);
+  hb_test_add (test_types_script);
+  hb_test_add (test_types_language);
+
+  return hb_test_run();
+}
diff --git a/test/api/test-cplusplus.cc b/test/api/test-cplusplus.cc
new file mode 100644
index 0000000..3313d74
--- /dev/null
+++ b/test/api/test-cplusplus.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+/* This file tests that all headers can be included from .cc files */
+
+
+#include "test-c.c"
diff --git a/test/api/test-font.c b/test/api/test-font.c
new file mode 100644
index 0000000..40540c4
--- /dev/null
+++ b/test/api/test-font.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-font.h */
+
+
+static const char test_data[] = "test\0data";
+
+
+static void
+test_face_empty (void)
+{
+  g_assert (hb_face_get_empty ());
+  g_assert (hb_face_get_empty () == hb_face_create (hb_blob_get_empty (), 0));
+  g_assert (hb_face_get_empty () == hb_face_create (NULL, 0));
+
+  g_assert (hb_face_reference_table (hb_face_get_empty (), HB_TAG ('h','e','a','d')) == hb_blob_get_empty ());
+
+  g_assert_cmpint (hb_face_get_upem (hb_face_get_empty ()), ==, 1000);
+}
+
+static void
+test_face_create (void)
+{
+  hb_face_t *face;
+  hb_blob_t *blob;
+
+  blob = hb_blob_create (test_data, sizeof (test_data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+  face = hb_face_create (blob, 0);
+  hb_blob_destroy (blob);
+
+  g_assert (hb_face_reference_table (face, HB_TAG ('h','e','a','d')) == hb_blob_get_empty ());
+
+  g_assert_cmpint (hb_face_get_upem (face), ==, 1000);
+
+  hb_face_destroy (face);
+}
+
+
+static void
+free_up (void *user_data)
+{
+  int *freed = (int *) user_data;
+
+  g_assert (!*freed);
+
+  (*freed)++;
+}
+
+static hb_blob_t *
+get_table (hb_face_t *face, hb_tag_t tag, void *user_data)
+{
+  if (tag == HB_TAG ('a','b','c','d'))
+    return hb_blob_create (test_data, sizeof (test_data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+
+  return hb_blob_get_empty ();
+}
+
+static void
+test_face_createfortables (void)
+{
+  hb_face_t *face;
+  hb_blob_t *blob;
+  const char *data;
+  unsigned int len;
+  int freed = 0;
+
+  face = hb_face_create_for_tables (get_table, &freed, free_up);
+  g_assert (!freed);
+
+  g_assert (hb_face_reference_table (face, HB_TAG ('h','e','a','d')) == hb_blob_get_empty ());
+
+  blob = hb_face_reference_table (face, HB_TAG ('a','b','c','d'));
+  g_assert (blob != hb_blob_get_empty ());
+
+  data = hb_blob_get_data (blob, &len);
+  g_assert_cmpint (len, ==, sizeof (test_data));
+  g_assert (0 == memcmp (data, test_data, sizeof (test_data)));
+  hb_blob_destroy (blob);
+
+  g_assert_cmpint (hb_face_get_upem (face), ==, 1000);
+
+  hb_face_destroy (face);
+  g_assert (freed);
+}
+
+static void
+_test_font_nil_funcs (hb_font_t *font)
+{
+  hb_codepoint_t glyph;
+  hb_position_t x, y;
+  hb_glyph_extents_t extents;
+
+  x = y = 13;
+  g_assert (!hb_font_get_glyph_contour_point (font, 17, 2, &x, &y));
+  g_assert_cmpint (x, ==, 0);
+  g_assert_cmpint (y, ==, 0);
+
+  x = hb_font_get_glyph_h_advance (font, 17);
+  g_assert_cmpint (x, ==, 0);
+
+  extents.x_bearing = extents.y_bearing = 13;
+  extents.width = extents.height = 15;
+  hb_font_get_glyph_extents (font, 17, &extents);
+  g_assert_cmpint (extents.x_bearing, ==, 0);
+  g_assert_cmpint (extents.y_bearing, ==, 0);
+  g_assert_cmpint (extents.width, ==, 0);
+  g_assert_cmpint (extents.height, ==, 0);
+
+  glyph = 3;
+  g_assert (!hb_font_get_glyph (font, 17, 2, &glyph));
+  g_assert_cmpint (glyph, ==, 0);
+
+  x = 13;
+  x = hb_font_get_glyph_h_kerning (font, 17, 19);
+  g_assert_cmpint (x, ==, 0);
+}
+
+static void
+_test_fontfuncs_nil (hb_font_funcs_t *ffuncs)
+{
+  hb_blob_t *blob;
+  hb_face_t *face;
+  hb_font_t *font;
+  hb_font_t *subfont;
+  int freed = 0;
+
+  blob = hb_blob_create (test_data, sizeof (test_data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+  face = hb_face_create (blob, 0);
+  hb_blob_destroy (blob);
+  g_assert (!hb_face_is_immutable (face));
+  font = hb_font_create (face);
+  g_assert (font);
+  g_assert (hb_face_is_immutable (face));
+  hb_face_destroy (face);
+
+
+  hb_font_set_funcs (font, ffuncs, &freed, free_up);
+  g_assert_cmpint (freed, ==, 0);
+
+  _test_font_nil_funcs (font);
+
+  subfont = hb_font_create_sub_font (font);
+  g_assert (subfont);
+
+  g_assert_cmpint (freed, ==, 0);
+  hb_font_destroy (font);
+  g_assert_cmpint (freed, ==, 0);
+
+  _test_font_nil_funcs (subfont);
+
+  hb_font_destroy (subfont);
+  g_assert_cmpint (freed, ==, 1);
+}
+
+static void
+test_fontfuncs_empty (void)
+{
+  g_assert (hb_font_funcs_get_empty ());
+  g_assert (hb_font_funcs_is_immutable (hb_font_funcs_get_empty ()));
+  _test_fontfuncs_nil (hb_font_funcs_get_empty ());
+}
+
+static void
+test_fontfuncs_nil (void)
+{
+  hb_font_funcs_t *ffuncs;
+
+  ffuncs = hb_font_funcs_create ();
+
+  g_assert (!hb_font_funcs_is_immutable (ffuncs));
+  _test_fontfuncs_nil (hb_font_funcs_get_empty ());
+
+  hb_font_funcs_destroy (ffuncs);
+}
+
+static hb_bool_t
+contour_point_func1 (hb_font_t *font, void *font_data,
+		     hb_codepoint_t glyph, unsigned int point_index,
+		     hb_position_t *x, hb_position_t *y,
+		     void *user_data)
+{
+  if (glyph == 1) {
+    *x = 2;
+    *y = 3;
+    return TRUE;
+  }
+  if (glyph == 2) {
+    *x = 4;
+    *y = 5;
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static hb_bool_t
+contour_point_func2 (hb_font_t *font, void *font_data,
+		     hb_codepoint_t glyph, unsigned int point_index,
+		     hb_position_t *x, hb_position_t *y,
+		     void *user_data)
+{
+  if (glyph == 1) {
+    *x = 6;
+    *y = 7;
+    return TRUE;
+  }
+
+  return hb_font_get_glyph_contour_point (hb_font_get_parent (font),
+					  glyph, point_index, x, y);
+}
+
+static hb_position_t
+glyph_h_advance_func1 (hb_font_t *font, void *font_data,
+		       hb_codepoint_t glyph,
+		       void *user_data)
+{
+  if (glyph == 1)
+    return 8;
+
+  return 0;
+}
+
+static void
+test_fontfuncs_subclassing (void)
+{
+  hb_blob_t *blob;
+  hb_face_t *face;
+
+  hb_font_funcs_t *ffuncs1;
+  hb_font_funcs_t *ffuncs2;
+
+  hb_font_t *font1;
+  hb_font_t *font2;
+  hb_font_t *font3;
+
+  hb_position_t x;
+  hb_position_t y;
+
+  blob = hb_blob_create (test_data, sizeof (test_data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+  face = hb_face_create (blob, 0);
+  hb_blob_destroy (blob);
+  font1 = hb_font_create (face);
+  hb_face_destroy (face);
+  hb_font_set_scale (font1, 10, 10);
+
+  /* setup font1 */
+  ffuncs1 = hb_font_funcs_create ();
+  hb_font_funcs_set_glyph_contour_point_func (ffuncs1, contour_point_func1, NULL, NULL);
+  hb_font_funcs_set_glyph_h_advance_func (ffuncs1, glyph_h_advance_func1, NULL, NULL);
+  hb_font_set_funcs (font1, ffuncs1, NULL, NULL);
+  hb_font_funcs_destroy (ffuncs1);
+
+  x = y = 1;
+  g_assert (hb_font_get_glyph_contour_point_for_origin (font1, 1, 2, HB_DIRECTION_LTR, &x, &y));
+  g_assert_cmpint (x, ==, 2);
+  g_assert_cmpint (y, ==, 3);
+  g_assert (hb_font_get_glyph_contour_point_for_origin (font1, 2, 5, HB_DIRECTION_LTR, &x, &y));
+  g_assert_cmpint (x, ==, 4);
+  g_assert_cmpint (y, ==, 5);
+  g_assert (!hb_font_get_glyph_contour_point_for_origin (font1, 3, 7, HB_DIRECTION_RTL, &x, &y));
+  g_assert_cmpint (x, ==, 0);
+  g_assert_cmpint (y, ==, 0);
+  x = hb_font_get_glyph_h_advance (font1, 1);
+  g_assert_cmpint (x, ==, 8);
+  x = hb_font_get_glyph_h_advance (font1, 2);
+  g_assert_cmpint (x, ==, 0);
+
+
+  font2 = hb_font_create_sub_font (font1);
+  g_assert (hb_font_is_immutable (font1));
+  hb_font_destroy (font1);
+
+  /* setup font2 to override some funcs */
+  ffuncs2 = hb_font_funcs_create ();
+  hb_font_funcs_set_glyph_contour_point_func (ffuncs2, contour_point_func2, NULL, NULL);
+  hb_font_set_funcs (font2, ffuncs2, NULL, NULL);
+  hb_font_funcs_destroy (ffuncs2);
+
+  x = y = 1;
+  g_assert (hb_font_get_glyph_contour_point_for_origin (font2, 1, 2, HB_DIRECTION_LTR, &x, &y));
+  g_assert_cmpint (x, ==, 6);
+  g_assert_cmpint (y, ==, 7);
+  g_assert (hb_font_get_glyph_contour_point_for_origin (font2, 2, 5, HB_DIRECTION_RTL, &x, &y));
+  g_assert_cmpint (x, ==, 4);
+  g_assert_cmpint (y, ==, 5);
+  g_assert (!hb_font_get_glyph_contour_point_for_origin (font2, 3, 7, HB_DIRECTION_LTR, &x, &y));
+  g_assert_cmpint (x, ==, 0);
+  g_assert_cmpint (y, ==, 0);
+  x = hb_font_get_glyph_h_advance (font2, 1);
+  g_assert_cmpint (x, ==, 8);
+  x = hb_font_get_glyph_h_advance (font2, 2);
+  g_assert_cmpint (x, ==, 0);
+
+
+  font3 = hb_font_create_sub_font (font2);
+  g_assert (hb_font_is_immutable (font2));
+  hb_font_destroy (font2);
+
+  /* setup font3 to override scale */
+  hb_font_set_scale (font3, 20, 30);
+
+  x = y = 1;
+  g_assert (hb_font_get_glyph_contour_point_for_origin (font3, 1, 2, HB_DIRECTION_RTL, &x, &y));
+  g_assert_cmpint (x, ==, 6*2);
+  g_assert_cmpint (y, ==, 7*3);
+  g_assert (hb_font_get_glyph_contour_point_for_origin (font3, 2, 5, HB_DIRECTION_LTR, &x, &y));
+  g_assert_cmpint (x, ==, 4*2);
+  g_assert_cmpint (y, ==, 5*3);
+  g_assert (!hb_font_get_glyph_contour_point_for_origin (font3, 3, 7, HB_DIRECTION_LTR, &x, &y));
+  g_assert_cmpint (x, ==, 0*2);
+  g_assert_cmpint (y, ==, 0*3);
+  x = hb_font_get_glyph_h_advance (font3, 1);
+  g_assert_cmpint (x, ==, 8*2);
+  x = hb_font_get_glyph_h_advance (font3, 2);
+  g_assert_cmpint (x, ==, 0*2);
+
+
+  hb_font_destroy (font3);
+}
+
+
+static void
+test_font_empty (void)
+{
+  g_assert (hb_font_get_empty ());
+  g_assert (hb_font_get_empty () == hb_font_create (hb_face_get_empty ()));
+  g_assert (hb_font_get_empty () == hb_font_create (NULL));
+  g_assert (hb_font_get_empty () == hb_font_create_sub_font (NULL));
+  g_assert (hb_font_is_immutable (hb_font_get_empty ()));
+
+  g_assert (hb_font_get_face (hb_font_get_empty ()) == hb_face_get_empty ());
+  g_assert (hb_font_get_parent (hb_font_get_empty ()) == NULL);
+}
+
+static void
+test_font_properties (void)
+{
+  hb_blob_t *blob;
+  hb_face_t *face;
+  hb_font_t *font;
+  hb_font_t *subfont;
+  int x_scale, y_scale;
+  unsigned int x_ppem, y_ppem;
+
+  blob = hb_blob_create (test_data, sizeof (test_data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+  face = hb_face_create (blob, 0);
+  hb_blob_destroy (blob);
+  font = hb_font_create (face);
+  hb_face_destroy (face);
+
+
+  g_assert (hb_font_get_face (font) == face);
+  g_assert (hb_font_get_parent (font) == NULL);
+
+
+  /* Check scale */
+
+  hb_font_get_scale (font, NULL, NULL);
+  x_scale = y_scale = 13;
+  hb_font_get_scale (font, &x_scale, NULL);
+  g_assert_cmpint (x_scale, ==, 0);
+  x_scale = y_scale = 13;
+  hb_font_get_scale (font, NULL, &y_scale);
+  g_assert_cmpint (y_scale, ==, 0);
+  x_scale = y_scale = 13;
+  hb_font_get_scale (font, &x_scale, &y_scale);
+  g_assert_cmpint (x_scale, ==, 0);
+  g_assert_cmpint (y_scale, ==, 0);
+
+  hb_font_set_scale (font, 17, 19);
+
+  x_scale = y_scale = 13;
+  hb_font_get_scale (font, &x_scale, &y_scale);
+  g_assert_cmpint (x_scale, ==, 17);
+  g_assert_cmpint (y_scale, ==, 19);
+
+
+  /* Check ppem */
+
+  hb_font_get_ppem (font, NULL, NULL);
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (font, &x_ppem, NULL);
+  g_assert_cmpint (x_ppem, ==, 0);
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (font, NULL, &y_ppem);
+  g_assert_cmpint (y_ppem, ==, 0);
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (font, &x_ppem, &y_ppem);
+  g_assert_cmpint (x_ppem, ==, 0);
+  g_assert_cmpint (y_ppem, ==, 0);
+
+  hb_font_set_ppem (font, 17, 19);
+
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (font, &x_ppem, &y_ppem);
+  g_assert_cmpint (x_ppem, ==, 17);
+  g_assert_cmpint (y_ppem, ==, 19);
+
+
+  /* Check immutable */
+
+  g_assert (!hb_font_is_immutable (font));
+  hb_font_make_immutable (font);
+  g_assert (hb_font_is_immutable (font));
+
+  hb_font_set_scale (font, 10, 12);
+  x_scale = y_scale = 13;
+  hb_font_get_scale (font, &x_scale, &y_scale);
+  g_assert_cmpint (x_scale, ==, 17);
+  g_assert_cmpint (y_scale, ==, 19);
+
+  hb_font_set_ppem (font, 10, 12);
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (font, &x_ppem, &y_ppem);
+  g_assert_cmpint (x_ppem, ==, 17);
+  g_assert_cmpint (y_ppem, ==, 19);
+
+
+  /* sub_font now */
+  subfont = hb_font_create_sub_font (font);
+  hb_font_destroy (font);
+
+  g_assert (hb_font_get_parent (subfont) == font);
+  g_assert (hb_font_get_face (subfont) == face);
+
+  /* scale */
+  x_scale = y_scale = 13;
+  hb_font_get_scale (subfont, &x_scale, &y_scale);
+  g_assert_cmpint (x_scale, ==, 17);
+  g_assert_cmpint (y_scale, ==, 19);
+  hb_font_set_scale (subfont, 10, 12);
+  x_scale = y_scale = 13;
+  hb_font_get_scale (subfont, &x_scale, &y_scale);
+  g_assert_cmpint (x_scale, ==, 10);
+  g_assert_cmpint (y_scale, ==, 12);
+  x_scale = y_scale = 13;
+  hb_font_get_scale (font, &x_scale, &y_scale);
+  g_assert_cmpint (x_scale, ==, 17);
+  g_assert_cmpint (y_scale, ==, 19);
+
+  /* ppem */
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (subfont, &x_ppem, &y_ppem);
+  g_assert_cmpint (x_ppem, ==, 17);
+  g_assert_cmpint (y_ppem, ==, 19);
+  hb_font_set_ppem (subfont, 10, 12);
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (subfont, &x_ppem, &y_ppem);
+  g_assert_cmpint (x_ppem, ==, 10);
+  g_assert_cmpint (y_ppem, ==, 12);
+  x_ppem = y_ppem = 13;
+  hb_font_get_ppem (font, &x_ppem, &y_ppem);
+  g_assert_cmpint (x_ppem, ==, 17);
+  g_assert_cmpint (y_ppem, ==, 19);
+
+  hb_font_destroy (subfont);
+}
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_face_empty);
+  hb_test_add (test_face_create);
+  hb_test_add (test_face_createfortables);
+
+  hb_test_add (test_fontfuncs_empty);
+  hb_test_add (test_fontfuncs_nil);
+  hb_test_add (test_fontfuncs_subclassing);
+
+  hb_test_add (test_font_empty);
+  hb_test_add (test_font_properties);
+
+  return hb_test_run();
+}
diff --git a/test/api/test-object.c b/test/api/test-object.c
new file mode 100644
index 0000000..66e8d33
--- /dev/null
+++ b/test/api/test-object.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-object-private.h */
+
+
+static void *
+create_blob (void)
+{
+  static char data[] = "test data";
+  return hb_blob_create (data, sizeof (data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+}
+static void *
+create_blob_inert (void)
+{
+  return hb_blob_create (NULL, 0, HB_MEMORY_MODE_DUPLICATE, NULL, NULL);
+}
+
+static void *
+create_buffer (void)
+{
+  return hb_buffer_create ();
+}
+static void *
+create_buffer_inert (void)
+{
+  return NULL;
+}
+
+static void *
+create_face (void)
+{
+  hb_blob_t *blob = (hb_blob_t *) create_blob ();
+  hb_face_t *face = hb_face_create (blob, 0);
+  hb_blob_destroy (blob);
+  return face;
+}
+static void *
+create_face_inert (void)
+{
+  return hb_face_create (hb_blob_get_empty (), 0);
+}
+
+static void *
+create_font (void)
+{
+  hb_face_t *face = (hb_face_t *) create_face ();
+  hb_font_t *font = hb_font_create (face);
+  hb_face_destroy (face);
+  return font;
+}
+static void *
+create_font_inert (void)
+{
+  return hb_font_create (hb_face_get_empty ());
+}
+
+static void *
+create_font_funcs (void)
+{
+  return hb_font_funcs_create ();
+}
+static void *
+create_font_funcs_inert (void)
+{
+  return NULL;
+}
+
+static void *
+create_unicode_funcs (void)
+{
+  return hb_unicode_funcs_create (NULL);
+}
+static void *
+create_unicode_funcs_inert (void)
+{
+  return hb_unicode_funcs_get_default ();
+}
+
+
+
+typedef void     *(*create_func_t)         (void);
+typedef void     *(*reference_func_t)      (void *obj);
+typedef void      (*destroy_func_t)        (void *obj);
+typedef hb_bool_t (*set_user_data_func_t)  (void *obj, hb_user_data_key_t *key, void *data, hb_destroy_func_t destroy, hb_bool_t replace);
+typedef void *    (*get_user_data_func_t)  (void *obj, hb_user_data_key_t *key);
+typedef void      (*make_immutable_func_t) (void *obj);
+typedef hb_bool_t (*is_immutable_func_t)   (void *obj);
+
+typedef struct {
+  create_func_t          create;
+  create_func_t          create_inert;
+  create_func_t          get_empty;
+  reference_func_t       reference;
+  destroy_func_t         destroy;
+  set_user_data_func_t   set_user_data;
+  get_user_data_func_t   get_user_data;
+  make_immutable_func_t  make_immutable;
+  is_immutable_func_t    is_immutable;
+  const char            *name;
+} object_t;
+
+#define OBJECT_WITHOUT_IMMUTABILITY(name) \
+  { \
+    (create_func_t)         create_##name, \
+    (create_func_t)         create_##name##_inert, \
+    (create_func_t)         hb_##name##_get_empty, \
+    (reference_func_t)      hb_##name##_reference, \
+    (destroy_func_t)        hb_##name##_destroy, \
+    (set_user_data_func_t)  hb_##name##_set_user_data, \
+    (get_user_data_func_t)  hb_##name##_get_user_data, \
+    (make_immutable_func_t) NULL, \
+    (is_immutable_func_t)   NULL, \
+    #name, \
+  }
+#define OBJECT_WITH_IMMUTABILITY(name) \
+  { \
+    (create_func_t)         create_##name, \
+    (create_func_t)         create_##name##_inert, \
+    (create_func_t)         hb_##name##_get_empty, \
+    (reference_func_t)      hb_##name##_reference, \
+    (destroy_func_t)        hb_##name##_destroy, \
+    (set_user_data_func_t)  hb_##name##_set_user_data, \
+    (get_user_data_func_t)  hb_##name##_get_user_data, \
+    (make_immutable_func_t) hb_##name##_make_immutable, \
+    (is_immutable_func_t)   hb_##name##_is_immutable, \
+    #name, \
+  }
+static const object_t objects[] =
+{
+  OBJECT_WITHOUT_IMMUTABILITY (buffer),
+  OBJECT_WITH_IMMUTABILITY (blob),
+  OBJECT_WITH_IMMUTABILITY (face),
+  OBJECT_WITH_IMMUTABILITY (font),
+  OBJECT_WITH_IMMUTABILITY (font_funcs),
+  OBJECT_WITH_IMMUTABILITY (unicode_funcs)
+};
+#undef OBJECT
+
+
+#define MAGIC0 0x12345678
+#define MAGIC1 0x76543210
+
+typedef struct {
+  int value;
+  gboolean freed;
+} data_t;
+
+static int global_data;
+
+static void global_free_up (void *p G_GNUC_UNUSED)
+{
+  global_data++;
+}
+
+static void free_up0 (void *p)
+{
+  data_t *data = (data_t *) p;
+
+  g_assert_cmphex (data->value, ==, MAGIC0);
+  g_assert (!data->freed);
+  data->freed = TRUE;
+}
+
+static void free_up1 (void *p)
+{
+  data_t *data = (data_t *) p;
+
+  g_assert_cmphex (data->value, ==, MAGIC1);
+  g_assert (!data->freed);
+  data->freed = TRUE;
+}
+
+
+typedef struct {
+  const object_t *klass;
+  void *object;
+  hb_user_data_key_t key;
+} deadlock_test_t;
+
+static void free_deadlock_test (void *p)
+{
+  deadlock_test_t *t = (deadlock_test_t *) p;
+
+  g_assert (NULL == t->klass->get_user_data (t->object, &t->key));
+}
+
+
+static void
+test_object (void)
+{
+  unsigned int i;
+
+  for (i = 0; i < G_N_ELEMENTS (objects); i++) {
+    const object_t *o = &objects[i];
+    void *obj;
+    hb_user_data_key_t key[2];
+
+    {
+      unsigned int j;
+      data_t data[2] = {{MAGIC0, FALSE}, {MAGIC1, FALSE}};
+      deadlock_test_t deadlock_test;
+
+      g_test_message ("Testing object %s", o->name);
+
+      g_test_message ("->create()");
+      obj = o->create ();
+      g_assert (obj);
+
+      g_assert (obj == o->reference (obj));
+      o->destroy (obj);
+
+      if (o->is_immutable)
+	g_assert (!o->is_immutable (obj));
+
+      g_assert (o->set_user_data (obj, &key[0], &data[0], free_up0, TRUE));
+      g_assert (o->get_user_data (obj, &key[0]) == &data[0]);
+
+      if (o->is_immutable) {
+	o->make_immutable (obj);
+	g_assert (o->is_immutable (obj));
+      }
+
+      /* Should still work even if object is made immutable */
+      g_assert (o->set_user_data (obj, &key[1], &data[1], free_up1, TRUE));
+      g_assert (o->get_user_data (obj, &key[1]) == &data[1]);
+
+      g_assert (!o->set_user_data (obj, NULL, &data[0], free_up0, TRUE));
+      g_assert (o->get_user_data (obj, &key[0]) == &data[0]);
+      g_assert (o->set_user_data (obj, &key[0], &data[1], NULL, TRUE));
+      g_assert (data[0].freed);
+      g_assert (o->get_user_data (obj, &key[0]) == &data[1]);
+      g_assert (!data[1].freed);
+
+      data[0].freed = FALSE;
+      g_assert (o->set_user_data (obj, &key[0], &data[0], free_up0, TRUE));
+      g_assert (!data[0].freed);
+      g_assert (o->set_user_data (obj, &key[0], NULL, NULL, TRUE));
+      g_assert (data[0].freed);
+
+      data[0].freed = FALSE;
+      global_data = 0;
+      g_assert (o->set_user_data (obj, &key[0], &data[0], free_up0, TRUE));
+      g_assert (!o->set_user_data (obj, &key[0], &data[0], free_up0, FALSE));
+      g_assert_cmpuint (global_data, ==, 0);
+      g_assert (o->set_user_data (obj, &key[0], NULL, global_free_up, TRUE));
+      g_assert_cmpuint (global_data, ==, 0);
+      g_assert (o->set_user_data (obj, &key[0], NULL, NULL, TRUE));
+      g_assert_cmpuint (global_data, ==, 1);
+
+      global_data = 0;
+      for (j = 2; j < 1000; j++)
+	g_assert (o->set_user_data (obj, &key[j], &data[j], global_free_up, TRUE));
+      for (j = 2; j < 1000; j++)
+	g_assert (o->get_user_data (obj, &key[j]) == &data[j]);
+      for (j = 100; j < 1000; j++)
+	g_assert (o->set_user_data (obj, &key[j], NULL, NULL, TRUE));
+      for (j = 2; j < 100; j++)
+	g_assert (o->get_user_data (obj, &key[j]) == &data[j]);
+      for (j = 100; j < 1000; j++)
+	g_assert (!o->get_user_data (obj, &key[j]));
+      g_assert_cmpuint (global_data, ==, 900);
+
+      /* Test set_user_data where the destroy() func calls user_data functions.
+       * Make sure it doesn't deadlock or corrupt memory. */
+      deadlock_test.klass = o;
+      deadlock_test.object = obj;
+      g_assert (o->set_user_data (obj, &deadlock_test.key, &deadlock_test, free_deadlock_test, TRUE));
+      g_assert (o->set_user_data (obj, &deadlock_test.key, NULL, NULL, TRUE));
+
+      g_assert (!data[1].freed);
+      o->destroy (obj);
+      g_assert (data[0].freed);
+      g_assert (data[1].freed);
+      g_assert_cmpuint (global_data, ==, 1000-2);
+    }
+
+    {
+      data_t data[2] = {{MAGIC0, FALSE}, {MAGIC1, FALSE}};
+
+      g_test_message ("->get_empty()");
+      obj = o->get_empty ();
+      g_assert (obj);
+
+      g_assert (obj == o->reference (obj));
+      o->destroy (obj);
+
+      if (o->is_immutable)
+	g_assert (o->is_immutable (obj));
+
+      g_assert (!o->set_user_data (obj, &key[0], &data[0], free_up0, TRUE));
+      g_assert (!o->get_user_data (obj, &key[0]));
+
+      o->destroy (obj);
+      o->destroy (obj);
+      o->destroy (obj);
+      o->destroy (obj);
+      o->destroy (obj);
+
+      g_assert (!data[0].freed);
+    }
+
+    {
+      data_t data[2] = {{MAGIC0, FALSE}, {MAGIC1, FALSE}};
+
+      g_test_message ("->create_inert()");
+      obj = o->create_inert ();
+      if (!obj)
+	continue;
+      if (obj == o->get_empty ())
+        continue; /* Tested already */
+
+      g_assert (obj == o->reference (obj));
+      o->destroy (obj);
+
+      if (o->is_immutable)
+	g_assert (o->is_immutable (obj));
+
+      g_assert (!o->set_user_data (obj, &key[0], &data[0], free_up0, TRUE));
+      g_assert (!o->get_user_data (obj, &key[0]));
+
+      o->destroy (obj);
+      o->destroy (obj);
+      o->destroy (obj);
+      o->destroy (obj);
+      o->destroy (obj);
+
+      g_assert (!data[0].freed);
+    }
+  }
+}
+
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_object);
+
+  return hb_test_run ();
+}
diff --git a/test/api/test-ot-tag.c b/test/api/test-ot-tag.c
new file mode 100644
index 0000000..81b6678
--- /dev/null
+++ b/test/api/test-ot-tag.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+#include <hb-ot.h>
+
+/* Unit tests for hb-ot-tag.h */
+
+
+/* https://www.microsoft.com/typography/otspec/scripttags.htm */
+
+static void
+test_simple_tags (const char *s, hb_script_t script)
+{
+  hb_script_t tag;
+  hb_script_t t1, t2;
+
+  g_test_message ("Testing script %c%c%c%c: tag %s", HB_UNTAG (hb_script_to_iso15924_tag (script)), s);
+  tag = hb_tag_from_string (s, -1);
+
+  hb_ot_tags_from_script (script, &t1, &t2);
+
+  g_assert_cmphex (t1, ==, tag);
+  g_assert_cmphex (t2, ==, HB_OT_TAG_DEFAULT_SCRIPT);
+
+  g_assert_cmphex (hb_ot_tag_to_script (tag), ==, script);
+}
+
+static void
+test_indic_tags (const char *s1, const char *s2, hb_script_t script)
+{
+  hb_script_t tag1, tag2;
+  hb_script_t t1, t2;
+
+  g_test_message ("Testing script %c%c%c%c: new tag %s, old tag %s", HB_UNTAG (hb_script_to_iso15924_tag (script)), s1, s2);
+  tag1 = hb_tag_from_string (s1, -1);
+  tag2 = hb_tag_from_string (s2, -1);
+
+  hb_ot_tags_from_script (script, &t1, &t2);
+
+  g_assert_cmphex (t1, ==, tag1);
+  g_assert_cmphex (t2, ==, tag2);
+
+  g_assert_cmphex (hb_ot_tag_to_script (tag1), ==, script);
+  g_assert_cmphex (hb_ot_tag_to_script (tag2), ==, script);
+}
+
+static void
+test_ot_tag_script_degenerate (void)
+{
+  hb_script_t t1, t2;
+
+  g_assert_cmphex (HB_TAG_CHAR4 ("DFLT"), ==, HB_OT_TAG_DEFAULT_SCRIPT);
+
+  /* HIRAGANA and KATAKANA both map to 'kana' */
+  test_simple_tags ("kana", HB_SCRIPT_KATAKANA);
+  hb_ot_tags_from_script (HB_SCRIPT_HIRAGANA, &t1, &t2);
+  g_assert_cmphex (t1, ==, HB_TAG_CHAR4 ("kana"));
+  g_assert_cmphex (t2, ==, HB_OT_TAG_DEFAULT_SCRIPT);
+
+  test_simple_tags ("DFLT", HB_SCRIPT_INVALID);
+
+  /* Spaces are replaced */
+  g_assert_cmphex (hb_ot_tag_to_script (HB_TAG_CHAR4 ("be  ")), ==, hb_script_from_string ("Beee", -1));
+}
+
+static void
+test_ot_tag_script_simple (void)
+{
+  /* Arbitrary non-existent script */
+  test_simple_tags ("wwyz", hb_script_from_string ("wWyZ", -1));
+
+  /* These we don't really care about */
+  test_simple_tags ("zyyy", HB_SCRIPT_COMMON);
+  test_simple_tags ("zinh", HB_SCRIPT_INHERITED);
+  test_simple_tags ("zzzz", HB_SCRIPT_UNKNOWN);
+
+  test_simple_tags ("arab", HB_SCRIPT_ARABIC);
+  test_simple_tags ("copt", HB_SCRIPT_COPTIC);
+  test_simple_tags ("kana", HB_SCRIPT_KATAKANA);
+  test_simple_tags ("latn", HB_SCRIPT_LATIN);
+
+  /* These are trickier since their OT script tags have space. */
+  test_simple_tags ("lao ", HB_SCRIPT_LAO);
+  test_simple_tags ("yi  ", HB_SCRIPT_YI);
+  /* Unicode-5.0 additions */
+  test_simple_tags ("nko ", HB_SCRIPT_NKO);
+  /* Unicode-5.1 additions */
+  test_simple_tags ("vai ", HB_SCRIPT_VAI);
+
+  /* https://www.microsoft.com/typography/otspec160/scripttagsProposed.htm */
+
+  /* Unicode-5.2 additions */
+  test_simple_tags ("mtei", HB_SCRIPT_MEETEI_MAYEK);
+  /* Unicode-6.0 additions */
+  test_simple_tags ("mand", HB_SCRIPT_MANDAIC);
+}
+
+static void
+test_ot_tag_script_indic (void)
+{
+  test_indic_tags ("bng2", "beng", HB_SCRIPT_BENGALI);
+  test_indic_tags ("dev2", "deva", HB_SCRIPT_DEVANAGARI);
+  test_indic_tags ("gjr2", "gujr", HB_SCRIPT_GUJARATI);
+  test_indic_tags ("gur2", "guru", HB_SCRIPT_GURMUKHI);
+  test_indic_tags ("knd2", "knda", HB_SCRIPT_KANNADA);
+  test_indic_tags ("mlm2", "mlym", HB_SCRIPT_MALAYALAM);
+  test_indic_tags ("ory2", "orya", HB_SCRIPT_ORIYA);
+  test_indic_tags ("tml2", "taml", HB_SCRIPT_TAMIL);
+  test_indic_tags ("tel2", "telu", HB_SCRIPT_TELUGU);
+}
+
+
+
+/* https://www.microsoft.com/typography/otspec/languagetags.htm */
+
+static void
+test_language_two_way (const char *tag_s, const char *lang_s)
+{
+  hb_language_t lang = hb_language_from_string (lang_s, -1);
+  hb_tag_t tag = hb_tag_from_string (tag_s, -1);
+
+  g_test_message ("Testing language %s <-> tag %s", lang_s, tag_s);
+
+  g_assert_cmphex (tag, ==, hb_ot_tag_from_language (lang));
+  g_assert (lang == hb_ot_tag_to_language (tag));
+}
+
+static void
+test_tag_from_language (const char *tag_s, const char *lang_s)
+{
+  hb_language_t lang = hb_language_from_string (lang_s, -1);
+  hb_tag_t tag = hb_tag_from_string (tag_s, -1);
+
+  g_test_message ("Testing language %s -> tag %s", lang_s, tag_s);
+
+  g_assert_cmphex (tag, ==, hb_ot_tag_from_language (lang));
+}
+
+static void
+test_tag_to_language (const char *tag_s, const char *lang_s)
+{
+  hb_language_t lang = hb_language_from_string (lang_s, -1);
+  hb_tag_t tag = hb_tag_from_string (tag_s, -1);
+
+  g_test_message ("Testing tag %s -> language %s", tag_s, lang_s);
+
+  g_assert (lang == hb_ot_tag_to_language (tag));
+}
+
+static void
+test_ot_tag_language (void)
+{
+  g_assert_cmphex (HB_TAG_CHAR4 ("dflt"), ==, HB_OT_TAG_DEFAULT_LANGUAGE);
+  test_language_two_way ("dflt", NULL);
+
+  test_language_two_way ("ARA", "ar");
+
+  test_language_two_way ("AZE", "az");
+  test_tag_from_language ("AZE", "az-ir");
+  test_tag_from_language ("AZE", "az-az");
+
+  test_language_two_way ("ENG", "en");
+  test_tag_from_language ("ENG", "en_US");
+
+  test_language_two_way ("EVN", "eve");
+
+  test_language_two_way ("FAR", "fa");
+  test_tag_from_language ("FAR", "fa_IR");
+
+  test_language_two_way ("ZHH", "zh-hk"); /* Chinese (Hong Kong) */
+
+  test_tag_from_language ("ZHS", "zh-cn"); /* Chinese (China) */
+  test_tag_from_language ("ZHS", "zh-sg"); /* Chinese (Singapore) */
+  test_tag_from_language ("ZHT", "zh-mo"); /* Chinese (Macao) */
+  test_tag_from_language ("ZHT", "zh-tw"); /* Chinese (Taiwan) */
+
+  test_tag_from_language ("ZHS", "zh"); /* Chinese */
+  test_tag_from_language ("ZHS", "zh-xx");
+
+  test_tag_to_language ("ZHS", "zh-x-hbotzhs");
+  test_tag_to_language ("ZHT", "zh-x-hbotzht");
+  test_tag_to_language ("ZHP", "zh-x-hbotzhp");
+
+  test_language_two_way ("ABC", "x-hbotabc");
+  test_tag_from_language ("ABC", "asdf-asdf-wer-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "asdf-asdf-wer-x-hbotabc");
+  test_tag_from_language ("ABCD", "asdf-asdf-wer-x-hbotabcd");
+
+  test_tag_from_language ("dflt", "asdf-asdf-wer-x-hbot-zxc");
+
+  test_tag_from_language ("dflt", "xy");
+  test_tag_from_language ("XYZ", "xyz"); /* Unknown ISO 639-3 */
+  test_tag_from_language ("XYZ", "xyz-qw"); /* Unknown ISO 639-3 */
+
+  /* Test that x-hbot overrides the base language */
+  test_tag_from_language ("ABC", "fa-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "fa-ir-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "zh-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "zh-cn-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "zh-xy-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "xyz-xy-x-hbotabc-zxc");
+}
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_ot_tag_script_degenerate);
+  hb_test_add (test_ot_tag_script_simple);
+  hb_test_add (test_ot_tag_script_indic);
+
+  hb_test_add (test_ot_tag_language);
+
+  return hb_test_run();
+}
diff --git a/test/api/test-shape-complex.c b/test/api/test-shape-complex.c
new file mode 100644
index 0000000..8f25d8f
--- /dev/null
+++ b/test/api/test-shape-complex.c
@@ -0,0 +1,1189 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ * Copyright © 2008  Nokia Corporation and/or its subsidiary(-ies)
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for complex text shaping */
+
+/*
+ * This test provides a framework to test aspects of hb_shape() that are
+ * font-dependent.  Please add tests for any feature that fits that
+ * description.
+ */
+
+#include <hb-ft.h>
+
+typedef struct
+{
+  const char *font_file;
+  unsigned int face_index;
+  /* TODO add min/max face version */
+} font_data_t;
+
+static char *
+get_font_file (const font_data_t *font_data)
+{
+  return g_strdup_printf ("%s/fonts/%s", srcdir (), font_data->font_file);
+}
+
+
+typedef struct
+{
+  char           comments[64];
+  hb_codepoint_t characters[16];
+  hb_codepoint_t glyphs[16];
+} test_data_t;
+
+typedef struct
+{
+  const font_data_t font_data;
+  const test_data_t test_datas[];
+} test_set_t;
+
+typedef struct
+{
+  const font_data_t *font_data;
+  const test_data_t *test_data;
+} test_t;
+
+
+static const test_set_t tests_devanagari1 = {
+  {"raghu.ttf", 0},
+  {
+    { "Ka",
+      { 0x0915, 0 },
+      { 0x0080, 0 }
+    },
+    { "Ka Halant",
+      { 0x0915, 0x094d, 0 },
+      { 0x0080, 0x0051, 0 }
+    },
+    { "Ka Halant Ka",
+      { 0x0915, 0x094d, 0x0915, 0 },
+      { 0x00c8, 0x0080, 0 }
+    },
+    { "Ka MatraI",
+      { 0x0915, 0x093f, 0 },
+      { 0x01d1, 0x0080, 0 }
+    },
+    { "Ra Halant Ka",
+      { 0x0930, 0x094d, 0x0915, 0 },
+      { 0x0080, 0x005b, 0 }
+    },
+    { "Ra Halant Ka MatraI",
+      { 0x0930, 0x094d, 0x0915, 0x093f, 0 },
+      { 0x01d1, 0x0080, 0x005b, 0 }
+    },
+    { "MatraI",
+      { 0x093f, 0 },
+      { 0x01d4, 0x029c, 0 }
+    },
+    { "Ka Nukta",
+      { 0x0915, 0x093c, 0 },
+      { 0x00a4, 0 }
+    },
+    { "Ka Halant Ra",
+      { 0x0915, 0x094d, 0x0930, 0 },
+      { 0x0110, 0 }
+    },
+    { "Ka Halant Ra Halant Ka",
+      { 0x0915, 0x094d, 0x0930, 0x094d, 0x0915, 0 },
+      { 0x0158, 0x0080, 0 }
+    },
+    { "",
+      { 0x0930, 0x094d, 0x200d, 0 },
+      { 0x00e2, 0 }
+    },
+    { "",
+      { 0x0915, 0x094d, 0x0930, 0x094d, 0x200d, 0 },
+      { 0x0158, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_devanagari2 = {
+  {"mangal.ttf", 0},
+  {
+    { "Ka",
+      { 0x0915, 0 },
+      { 0x0080, 0 }
+    },
+    { "Ka Halant",
+      { 0x0915, 0x094d, 0 },
+      { 0x0080, 0x0051, 0 }
+    },
+    { "Ka Halant Ka",
+      { 0x0915, 0x094d, 0x0915, 0 },
+      { 0x00c8, 0x0080, 0 }
+    },
+    { "Ka MatraI",
+      { 0x0915, 0x093f, 0 },
+      { 0x01d1, 0x0080, 0 }
+    },
+    { "Ra Halant Ka",
+      { 0x0930, 0x094d, 0x0915, 0 },
+      { 0x0080, 0x005b, 0 }
+    },
+    { "Ra Halant Ka MatraI",
+      { 0x0930, 0x094d, 0x0915, 0x093f, 0 },
+      { 0x01d1, 0x0080, 0x005b, 0 }
+    },
+    { "MatraI",
+      { 0x093f, 0 },
+      { 0x01d4, 0x029c, 0 }
+    },
+    { "Ka Nukta",
+      { 0x0915, 0x093c, 0 },
+      { 0x00a4, 0 }
+    },
+    { "Ka Halant Ra",
+      { 0x0915, 0x094d, 0x0930, 0 },
+      { 0x0110, 0 }
+    },
+    { "Ka Halant Ra Halant Ka",
+      { 0x0915, 0x094d, 0x0930, 0x094d, 0x0915, 0 },
+      { 0x0158, 0x0080, 0 }
+    },
+    { "",
+      { 0x92b, 0x94d, 0x930, 0 },
+      { 0x125, 0 }
+    },
+    { "",
+      { 0x92b, 0x93c, 0x94d, 0x930, 0 },
+      { 0x149, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_devanagari3 = {
+  {"Lohit-Devanagari.ttf", 0},
+  {
+    { "Ka Halant Ssa Halant",
+      { 0x0915, 0x094D, 0x0937, 0x094D, 0},
+      { 0x00C4, 0x007D, 0}
+    },
+    { "Ja Halant Nya Halant",
+      { 0x091C, 0x094D, 0x091E, 0x094D, 0},
+      { 0x00C5, 0x007D, 0}
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_bengali1 = {
+  {"AkaashNormal.ttf", 0},
+  {
+    { "Ka",
+      { 0x0995, 0 },
+      { 0x0151, 0 }
+    },
+    { "Ka Halant",
+      { 0x0995, 0x09cd, 0 },
+      { 0x0151, 0x017d, 0 }
+    },
+    { "Ka Halant Ka",
+      { 0x0995, 0x09cd, 0x0995, 0 },
+      { 0x019b, 0 }
+    },
+    { "Ka MatraI",
+      { 0x0995, 0x09bf, 0 },
+      { 0x0173, 0x0151, 0 }
+    },
+    { "Ra Halant Ka",
+      { 0x09b0, 0x09cd, 0x0995, 0 },
+      { 0x0151, 0x0276, 0 }
+    },
+    { "Ra Halant Ka MatraI",
+      { 0x09b0, 0x09cd, 0x0995, 0x09bf, 0 },
+      { 0x0173, 0x0151, 0x0276, 0 }
+    },
+    { "Ka Nukta",
+      { 0x0995, 0x09bc, 0 },
+      { 0x0151, 0x0171, 0 }
+    },
+    { "Ka Halant Ra",
+      { 0x0995, 0x09cd, 0x09b0, 0 },
+      { 0x01f4, 0 }
+    },
+    { "Ka Halant Ra Halant Ka",
+      { 0x0995, 0x09cd, 0x09b0, 0x09cd, 0x0995, 0 },
+      { 0x025c, 0x0276, 0x0151, 0 }
+    },
+    { "Ya + Halant",
+      { 0x09af, 0x09cd, 0 },
+      { 0x016a, 0x017d, 0 }
+    },
+    { "Da Halant Ya -> Da Ya-Phala",
+      { 0x09a6, 0x09cd, 0x09af, 0 },
+      { 0x01e5, 0 }
+    },
+    { "A Halant Ya -> A Ya-phala",
+      { 0x0985, 0x09cd, 0x09af, 0 },
+      { 0x0145, 0x01cf, 0 }
+    },
+    { "Na Halant Ka",
+      { 0x09a8, 0x09cd, 0x0995, 0 },
+      { 0x026f, 0x0151, 0 }
+    },
+    { "Na Halant ZWNJ Ka",
+      { 0x09a8, 0x09cd, 0x200c, 0x0995, 0 },
+      { 0x0164, 0x017d, 0x0151, 0 }
+    },
+    { "Na Halant ZWJ Ka",
+      { 0x09a8, 0x09cd, 0x200d, 0x0995, 0 },
+      { 0x026f, 0x0151, 0 }
+    },
+    { "Ka Halant ZWNJ Ka",
+      { 0x0995, 0x09cd, 0x200c, 0x0995, 0 },
+      { 0x0151, 0x017d, 0x0151, 0 }
+    },
+    { "Ka Halant ZWJ Ka",
+      { 0x0995, 0x09cd, 0x200d, 0x0995, 0 },
+      { 0x025c, 0x0151, 0 }
+    },
+    { "Na Halant Ra",
+      { 0x09a8, 0x09cd, 0x09b0, 0 },
+      { 0x0207, 0 }
+    },
+    { "Na Halant ZWNJ Ra",
+      { 0x09a8, 0x09cd, 0x200c, 0x09b0, 0 },
+      { 0x0164, 0x017d, 0x016b, 0 }
+    },
+    { "Na Halant ZWJ Ra",
+      { 0x09a8, 0x09cd, 0x200d, 0x09b0, 0 },
+      { 0x026f, 0x016b, 0 }
+    },
+    { "Na Halant Ba",
+      { 0x09a8, 0x09cd, 0x09ac, 0 },
+      { 0x022f, 0 }
+    },
+    { "Na Halant ZWNJ Ba",
+      { 0x09a8, 0x09cd, 0x200c, 0x09ac, 0 },
+      { 0x0164, 0x017d, 0x0167, 0 }
+    },
+    { "Na Halant ZWJ Ba",
+      { 0x09a8, 0x09cd, 0x200d, 0x09ac, 0 },
+      { 0x026f, 0x0167, 0 }
+    },
+    { "Na Halant Dha",
+      { 0x09a8, 0x09cd, 0x09a7, 0 },
+      { 0x01d3, 0 }
+    },
+    { "Na Halant ZWNJ Dha",
+      { 0x09a8, 0x09cd, 0x200c, 0x09a7, 0 },
+      { 0x0164, 0x017d, 0x0163, 0 }
+    },
+    { "Na Halant ZWJ Dha",
+      { 0x09a8, 0x09cd, 0x200d, 0x09a7, 0 },
+      { 0x026f, 0x0163, 0 }
+    },
+    { "Ra Halant Ka MatraAU",
+      { 0x09b0, 0x09cd, 0x0995, 0x09cc, 0 },
+      { 0x0179, 0x0151, 0x0276, 0x017e, 0 }
+    },
+    { "Ra Halant Ba Halant Ba",
+      { 0x09b0, 0x09cd, 0x09ac, 0x09cd, 0x09ac, 0 },
+      { 0x0232, 0x0276, 0 }
+    },
+    { "",
+      { 0x9b0, 0x9cd, 0x995, 0x9be, 0x982, 0 },
+      { 0x151, 0x276, 0x172, 0x143, 0 }
+    },
+    { "",
+      { 0x9b0, 0x9cd, 0x995, 0x9be, 0x983, 0 },
+      { 0x151, 0x276, 0x172, 0x144, 0 }
+    },
+    /*  Test decomposed two part matras */
+    { "",
+      { 0x995, 0x9c7, 0x9be, 0 },
+      { 0x179, 0x151, 0x172, 0 }
+    },
+    { "",
+      { 0x995, 0x9c7, 0x9d7, 0 },
+      { 0x179, 0x151, 0x17e, 0 }
+    },
+    { "",
+      { 0x9b0, 0x9cd, 0x9ad, 0 },
+      { 0x168, 0x276, 0 }
+    },
+    { "",
+      { 0x9f0, 0x9cd, 0x9ad, 0 },
+      { 0x168, 0x276, 0 }
+    },
+    { "",
+      { 0x9f1, 0x9cd, 0x9ad, 0 },
+      { 0x191, 0x17d, 0x168, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_bengali2 = {
+  {"MuktiNarrow.ttf", 0},
+  {
+    { "Ka",
+      { 0x0995, 0 },
+      { 0x0073, 0 }
+    },
+    { "Ka Halant",
+      { 0x0995, 0x09cd, 0 },
+      { 0x00b9, 0 }
+    },
+    { "Ka Halant Ka",
+      { 0x0995, 0x09cd, 0x0995, 0 },
+      { 0x0109, 0 }
+    },
+    { "Ka MatraI",
+      { 0x0995, 0x09bf, 0 },
+      { 0x0095, 0x0073, 0 }
+    },
+    { "Ra Halant Ka",
+      { 0x09b0, 0x09cd, 0x0995, 0 },
+      { 0x0073, 0x00e1, 0 }
+    },
+    { "Ra Halant Ka MatraI",
+      { 0x09b0, 0x09cd, 0x0995, 0x09bf, 0 },
+      { 0x0095, 0x0073, 0x00e1, 0 }
+    },
+    { "MatraI",
+      { 0x09bf, 0 },
+      { 0x0095, 0x01c8, 0 }
+    },
+    { "Ka Nukta",
+      { 0x0995, 0x09bc, 0 },
+      { 0x0073, 0x0093, 0 }
+    },
+    { "Ka Halant Ra",
+      { 0x0995, 0x09cd, 0x09b0, 0 },
+      { 0x00e5, 0 }
+    },
+    { "Ka Halant Ra Halant Ka",
+      { 0x995, 0x9cd, 0x9b0, 0x9cd, 0x995, 0 },
+      { 0x234, 0x24e, 0x73, 0 }
+    },
+    { "Ya + Halant",
+      { 0x09af, 0x09cd, 0 },
+      { 0x00d2, 0 }
+    },
+    { "Da Halant Ya -> Da Ya-Phala",
+      { 0x09a6, 0x09cd, 0x09af, 0 },
+      { 0x0084, 0x00e2, 0 }
+    },
+    { "A Halant Ya -> A Ya-phala",
+      { 0x0985, 0x09cd, 0x09af, 0 },
+      { 0x0067, 0x00e2, 0 }
+    },
+    { "Na Halant Ka",
+      { 0x09a8, 0x09cd, 0x0995, 0 },
+      { 0x0188, 0 }
+    },
+    { "Na Halant ZWNJ Ka",
+      { 0x9a8, 0x9cd, 0x200c, 0x995, 0 },
+      { 0xcc, 0x73, 0 }
+    },
+    { "Na Halant ZWJ Ka",
+      { 0x9a8, 0x9cd, 0x200d, 0x995, 0 },
+      { 0x247, 0x73, 0 }
+    },
+    { "Ka Halant ZWNJ Ka",
+      { 0x9a8, 0x9cd, 0x200d, 0x995, 0 },
+      { 0x247, 0x73, 0 }
+    },
+    { "Ka Halant ZWJ Ka",
+      { 0x9a8, 0x9cd, 0x200d, 0x995, 0 },
+      { 0x247, 0x73, 0 }
+    },
+    { "Na Halant Ra",
+      { 0x09a8, 0x09cd, 0x09b0, 0 },
+      { 0x00f8, 0 }
+    },
+    { "Na Halant ZWNJ Ra",
+      { 0x09a8, 0x09cd, 0x200c, 0x09b0, 0 },
+      { 0xcc, 0x8d, 0 }
+    },
+    { "Na Halant ZWJ Ra",
+      { 0x9a8, 0x9cd, 0x200d, 0x9b0, 0 },
+      { 0x247, 0x8d, 0 }
+    },
+    { "Na Halant Ba",
+      { 0x09a8, 0x09cd, 0x09ac, 0 },
+      { 0x0139, 0 }
+    },
+    { "Na Halant ZWNJ Ba",
+      { 0x9a8, 0x9cd, 0x200c, 0x9ac, 0 },
+      { 0xcc, 0x89, 0 }
+    },
+    { "Na Halant ZWJ Ba",
+      { 0x9a8, 0x9cd, 0x200d, 0x9ac, 0 },
+      { 0x247, 0x89, 0 }
+    },
+    { "Na Halant Dha",
+      { 0x09a8, 0x09cd, 0x09a7, 0 },
+      { 0x0145, 0 }
+    },
+    { "Na Halant ZWNJ Dha",
+      { 0x09a8, 0x09cd, 0x200c, 0x09a7, 0 },
+      { 0xcc, 0x85, 0 }
+    },
+    { "Na Halant ZWJ Dha",
+      { 0x09a8, 0x09cd, 0x200d, 0x09a7, 0 },
+      { 0x247, 0x85, 0 }
+    },
+    { "Ra Halant Ka MatraAU",
+      { 0x9b0, 0x9cd, 0x995, 0x9cc, 0 },
+      { 0x232, 0x73, 0xe1, 0xa0, 0 }
+    },
+    { "Ra Halant Ba Halant Ba",
+      { 0x09b0, 0x09cd, 0x09ac, 0x09cd, 0x09ac, 0 },
+      { 0x013b, 0x00e1, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_bengali3 = {
+  {"LikhanNormal.ttf", 0},
+  {
+    { "",
+      { 0x09a8, 0x09cd, 0x09af, 0 },
+      { 0x01ca, 0 }
+    },
+    { "",
+      { 0x09b8, 0x09cd, 0x09af, 0 },
+      { 0x020e, 0 }
+    },
+    { "",
+      { 0x09b6, 0x09cd, 0x09af, 0 },
+      { 0x01f4, 0 }
+    },
+    { "",
+      { 0x09b7, 0x09cd, 0x09af, 0 },
+      { 0x01fe, 0 }
+    },
+    { "",
+      { 0x09b0, 0x09cd, 0x09a8, 0x09cd, 0x200d, 0 },
+      { 0x10b, 0x167, 0 }
+    },
+    { "",
+      { 0x9b0, 0x9cd, 0x9ad, 0 },
+      { 0xa1, 0x167, 0 }
+    },
+    { "",
+      { 0x9f0, 0x9cd, 0x9ad, 0 },
+      { 0xa1, 0x167, 0 }
+    },
+    { "",
+      { 0x9f1, 0x9cd, 0x9ad, 0 },
+      { 0x11c, 0xa1, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_gurmukhi = {
+  {"lohit_pa.ttf", 0},
+  {
+    { "",
+      { 0xA15, 0xA4D, 0xa39, 0 },
+      { 0x3b, 0x8b, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_oriya = {
+  {"utkalm.ttf", 0},
+  {
+    { "",
+      { 0xb15, 0xb4d, 0xb24, 0xb4d, 0xb30, 0 },
+      { 0x150, 0x125, 0 }
+    },
+    { "",
+      { 0xb24, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0 },
+      { 0x151, 0x120, 0 }
+    },
+    { "",
+      { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0 },
+      { 0x152, 0x120, 0 }
+    },
+    { "",
+      { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0 },
+      { 0x152, 0x120, 0 }
+    },
+    { "",
+      { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb30, 0 },
+      { 0x176, 0 }
+    },
+    { "",
+      { 0xb38, 0xb4d, 0xb24, 0xb4d, 0xb30, 0 },
+      { 0x177, 0 }
+    },
+    { "",
+      { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb30, 0xb4d, 0xb2f, 0 },
+      { 0x176, 0x124, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_tamil = {
+  {"akruti1.ttf", 0},
+  {
+    { "",
+      { 0x0b95, 0x0bc2, 0 },
+      { 0x004e, 0 }
+    },
+    { "",
+      { 0x0bae, 0x0bc2, 0 },
+      { 0x009e, 0 }
+    },
+    { "",
+      { 0x0b9a, 0x0bc2, 0 },
+      { 0x0058, 0 }
+    },
+    { "",
+      { 0x0b99, 0x0bc2, 0 },
+      { 0x0053, 0 }
+    },
+    { "",
+      { 0x0bb0, 0x0bc2, 0 },
+      { 0x00a8, 0 }
+    },
+    { "",
+      { 0x0ba4, 0x0bc2, 0 },
+      { 0x008e, 0 }
+    },
+    { "",
+      { 0x0b9f, 0x0bc2, 0 },
+      { 0x0062, 0 }
+    },
+    { "",
+      { 0x0b95, 0x0bc6, 0 },
+      { 0x000a, 0x0031, 0 }
+    },
+    { "",
+      { 0x0b95, 0x0bca, 0 },
+      { 0x000a, 0x0031, 0x0007, 0 }
+    },
+    { "",
+      { 0x0b95, 0x0bc6, 0x0bbe, 0 },
+      { 0x000a, 0x0031, 0x007, 0 }
+    },
+    { "",
+      { 0x0b95, 0x0bcd, 0x0bb7, 0 },
+      { 0x0049, 0 }
+    },
+    { "",
+      { 0x0b95, 0x0bcd, 0x0bb7, 0x0bca, 0 },
+      { 0x000a, 0x0049, 0x007, 0 }
+    },
+    { "",
+      { 0x0b95, 0x0bcd, 0x0bb7, 0x0bc6, 0x0bbe, 0 },
+      { 0x000a, 0x0049, 0x007, 0 }
+    },
+    { "",
+      { 0x0b9f, 0x0bbf, 0 },
+      { 0x005f, 0 }
+    },
+    { "",
+      { 0x0b9f, 0x0bc0, 0 },
+      { 0x0060, 0 }
+    },
+    { "",
+      { 0x0bb2, 0x0bc0, 0 },
+      { 0x00ab, 0 }
+    },
+    { "",
+      { 0x0bb2, 0x0bbf, 0 },
+      { 0x00aa, 0 }
+    },
+    { "",
+      { 0x0bb0, 0x0bcd, 0 },
+      { 0x00a4, 0 }
+    },
+    { "",
+      { 0x0bb0, 0x0bbf, 0 },
+      { 0x00a5, 0 }
+    },
+    { "",
+      { 0x0bb0, 0x0bc0, 0 },
+      { 0x00a6, 0 }
+    },
+    { "",
+      { 0x0b83, 0 },
+      { 0x0025, 0 }
+    },
+    { "",
+      { 0x0b83, 0x0b95, 0 },
+      { 0x0025, 0x0031, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_telugu = {
+  {"Pothana2000.ttf", 0},
+  {
+    { "",
+      { 0xc15, 0xc4d, 0 },
+      { 0xbb, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc37, 0 },
+      { 0x4b, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc37, 0xc4d, 0 },
+      { 0xe0, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc37, 0xc4d, 0xc23, 0 },
+      { 0x4b, 0x91, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc30, 0 },
+      { 0x5a, 0xb2, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc30, 0xc4d, 0 },
+      { 0xbb, 0xb2, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc30, 0xc4d, 0xc15, 0 },
+      { 0x5a, 0xb2, 0x83, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc30, 0xc3f, 0 },
+      { 0xe2, 0xb2, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc15, 0xc48, 0 },
+      { 0xe6, 0xb3, 0x83, 0 }
+    },
+    { "",
+      { 0xc15, 0xc4d, 0xc30, 0xc48, 0 },
+      { 0xe6, 0xb3, 0x9f, 0 }
+    },
+    { "",
+      { 0xc15, 0xc46, 0xc56, 0 },
+      { 0xe6, 0xb3, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_kannada1 = {
+  {"Sampige.ttf", 0},
+  {
+    { "",
+      { 0x0ca8, 0x0ccd, 0x0ca8, 0 },
+      { 0x0049, 0x00ba, 0 }
+    },
+    { "",
+      { 0x0ca8, 0x0ccd, 0x0ca1, 0 },
+      { 0x0049, 0x00b3, 0 }
+    },
+    { "",
+      { 0x0caf, 0x0cc2, 0 },
+      { 0x004f, 0x005d, 0 }
+    },
+    { "",
+      { 0x0ce0, 0 },
+      { 0x006a, 0 }
+    },
+    { "",
+      { 0x0ce6, 0x0ce7, 0x0ce8, 0 },
+      { 0x006b, 0x006c, 0x006d, 0 }
+    },
+    { "",
+      { 0x0cb5, 0x0ccb, 0 },
+      { 0x015f, 0x0067, 0 }
+    },
+    { "",
+      { 0x0cb0, 0x0ccd, 0x0cae, 0 },
+      { 0x004e, 0x0082, 0 }
+    },
+    { "",
+      { 0x0cb0, 0x0ccd, 0x0c95, 0 },
+      { 0x0036, 0x0082, 0 }
+    },
+    { "",
+      { 0x0c95, 0x0ccd, 0x0cb0, 0 },
+      { 0x0036, 0x00c1, 0 }
+    },
+    { "",
+      { 0x0cb0, 0x0ccd, 0x200d, 0x0c95, 0 },
+      { 0x0050, 0x00a7, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_kannada2 = {
+  {"tunga.ttf", 0},
+  {
+    { "",
+      { 0x0cb7, 0x0cc6, 0 },
+      { 0x00b0, 0x006c, 0 }
+    },
+    { "",
+      { 0x0cb7, 0x0ccd, 0 },
+      { 0x0163, 0 }
+    },
+    { "",
+      { 0xc95, 0xcbf, 0xcd5, 0 },
+      { 0x114, 0x73, 0 }
+    },
+    { "",
+      { 0xc95, 0xcc6, 0xcd5, 0 },
+      { 0x90, 0x6c, 0x73, 0 }
+    },
+    { "",
+      { 0xc95, 0xcc6, 0xcd6, 0 },
+      { 0x90, 0x6c, 0x74, 0 }
+    },
+    { "",
+      { 0xc95, 0xcc6, 0xcc2, 0 },
+      { 0x90, 0x6c, 0x69, 0 }
+    },
+    { "",
+      { 0xc95, 0xcca, 0xcd5, 0 },
+      { 0x90, 0x6c, 0x69, 0x73, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_malayalam1 = {
+  {"AkrutiMal2Normal.ttf", 0},
+  {
+    { "",
+      { 0x0d15, 0x0d46, 0 },
+      { 0x005e, 0x0034, 0 }
+    },
+    { "",
+      { 0x0d15, 0x0d47, 0 },
+      { 0x005f, 0x0034, 0 }
+    },
+    { "",
+      { 0x0d15, 0x0d4b, 0 },
+      { 0x005f, 0x0034, 0x0058, 0 }
+    },
+    { "",
+      { 0x0d15, 0x0d48, 0 },
+      { 0x0060, 0x0034, 0 }
+    },
+    { "",
+      { 0x0d15, 0x0d4a, 0 },
+      { 0x005e, 0x0034, 0x0058, 0 }
+    },
+    { "",
+      { 0x0d30, 0x0d4d, 0x0d15, 0 },
+      { 0x009e, 0x0034, 0 }
+    },
+    { "",
+      { 0x0d15, 0x0d4d, 0x0d35, 0 },
+      { 0x0034, 0x007a, 0 }
+    },
+    { "",
+      { 0x0d15, 0x0d4d, 0x0d2f, 0 },
+      { 0x0034, 0x00a2, 0 }
+    },
+    { "",
+      { 0x0d1f, 0x0d4d, 0x0d1f, 0 },
+      { 0x0069, 0 }
+    },
+    { "",
+      { 0x0d26, 0x0d4d, 0x0d26, 0 },
+      { 0x0074, 0 }
+    },
+    { "",
+      { 0x0d30, 0x0d4d, 0 },
+      { 0x009e, 0 }
+    },
+    { "",
+      { 0x0d30, 0x0d4d, 0x200c, 0 },
+      { 0x009e, 0 }
+    },
+    { "",
+      { 0x0d30, 0x0d4d, 0x200d, 0 },
+      { 0x009e, 0 }
+    },
+    { "",
+      { 0xd15, 0xd46, 0xd3e, 0 },
+      { 0x5e, 0x34, 0x58, 0 }
+    },
+    { "",
+      { 0xd15, 0xd47, 0xd3e, 0 },
+      { 0x5f, 0x34, 0x58, 0 }
+    },
+    { "",
+      { 0xd15, 0xd46, 0xd57, 0 },
+      { 0x5e, 0x34, 0x65, 0 }
+    },
+    { "",
+      { 0xd15, 0xd57, 0 },
+      { 0x34, 0x65, 0 }
+    },
+    { "",
+      { 0xd1f, 0xd4d, 0xd1f, 0xd41, 0xd4d, 0 },
+      { 0x69, 0x5b, 0x64, 0 }
+    },
+
+    {{0}}
+  }
+};
+
+static const test_set_t tests_malayalam2 = {
+  {"Rachana.ttf", 0},
+  {
+    { "",
+      { 0xd37, 0xd4d, 0xd1f, 0xd4d, 0xd30, 0xd40, 0 },
+      { 0x385, 0xa3, 0 }
+    },
+    { "",
+      { 0xd2f, 0xd4d, 0xd15, 0xd4d, 0xd15, 0xd41, 0 },
+      { 0x2ff, 0 }
+    },
+    { "",
+      { 0xd33, 0xd4d, 0xd33, 0 },
+      { 0x3f8, 0 }
+    },
+    { "",
+      { 0xd2f, 0xd4d, 0xd15, 0xd4d, 0xd15, 0xd41, 0 },
+      { 0x2ff, 0 }
+    },
+    { "",
+      { 0xd30, 0xd4d, 0x200d, 0xd35, 0xd4d, 0xd35, 0 },
+      { 0xf3, 0x350, 0 }
+    },
+
+    {{0}}
+  }
+};
+
+static const test_set_t tests_sinhala = {
+  {"FM-MalithiUW46.ttf", 0},
+  {
+    { "",
+      { 0xd9a, 0xdd9, 0xdcf, 0 },
+      { 0x4a, 0x61, 0x42, 0 }
+    },
+    { "",
+      { 0xd9a, 0xdd9, 0xddf, 0 },
+      { 0x4a, 0x61, 0x50, 0 }
+    },
+    { "",
+      { 0xd9a, 0xdd9, 0xdca, 0 },
+      { 0x4a, 0x62, 0 }
+    },
+    { "",
+      { 0xd9a, 0xddc, 0xdca, 0 },
+      { 0x4a, 0x61, 0x42, 0x41, 0 }
+    },
+    { "",
+      { 0xd9a, 0xdda, 0 },
+      { 0x4a, 0x62, 0 }
+    },
+    { "",
+      { 0xd9a, 0xddd, 0 },
+      { 0x4a, 0x61, 0x42, 0x41, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_khmer = {
+  {"KhmerOS.ttf", 0},
+  {
+    { "",
+      { 0x179a, 0x17cd, 0 },
+      { 0x24c, 0x27f, 0 }
+    },
+    { "",
+      { 0x179f, 0x17c5, 0 },
+      { 0x273, 0x203, 0 }
+    },
+    { "",
+      { 0x1790, 0x17d2, 0x1784, 0x17c3, 0 },
+      { 0x275, 0x242, 0x182, 0 }
+    },
+    { "",
+      { 0x179a, 0 },
+      { 0x24c, 0 }
+    },
+    { "",
+      { 0x1781, 0x17d2, 0x1798, 0x17c2, 0 },
+      { 0x274, 0x233, 0x197, 0 }
+    },
+    { "",
+      { 0x1798, 0x17b6, 0 },
+      { 0x1cb, 0 }
+    },
+    { "",
+      { 0x179a, 0x17b8, 0 },
+      { 0x24c, 0x26a, 0 }
+    },
+    { "",
+      { 0x1787, 0x17b6, 0 },
+      { 0x1ba, 0 }
+    },
+    { "",
+      { 0x1798, 0x17d2, 0x1796, 0x17bb, 0 },
+      { 0x24a, 0x195, 0x26d, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_nko = {
+  {"DejaVuSans.ttf", 0},
+  {
+    { "",
+      { 0x7ca, 0 },
+      { 0x5c1, 0 }
+    },
+    { "",
+      { 0x7ca, 0x7ca, 0 },
+      { 0x14d9, 0x14db, 0 }
+    },
+    { "",
+      { 0x7ca, 0x7fa, 0x7ca, 0 },
+      { 0x14d9, 0x5ec, 0x14db, 0 }
+    },
+    { "",
+      { 0x7ca, 0x7f3, 0x7ca, 0 },
+      { 0x14d9, 0x5e7, 0x14db, 0 }
+    },
+    { "",
+      { 0x7ca, 0x7f3, 0x7fa, 0x7ca, 0 },
+      { 0x14d9, 0x5ec, 0x5e7, 0x14db, 0 }
+    },
+    {{0}}
+  }
+};
+
+static const test_set_t tests_linearb = {
+  {"penuture.ttf", 0},
+  {
+    { "",
+      { 0xd800, 0xdc01, 0xd800, 0xdc02, 0xd800, 0xdc03,  0 },
+      { 0x5, 0x6, 0x7, 0 },
+    },
+    {{0}}
+  }
+};
+
+
+
+
+
+typedef struct {
+  FT_Library ft_library;
+  FT_Face ft_face;
+  hb_font_t *font;
+} ft_fixture_t;
+
+static void
+ft_fixture_init (ft_fixture_t *f, gconstpointer user_data)
+{
+  const test_t *test = user_data;
+  char *font_file = get_font_file (test->font_data);
+  FT_Error err;
+
+  FT_Init_FreeType (&f->ft_library);
+
+  err = FT_New_Face (f->ft_library, font_file, test->font_data->face_index, &f->ft_face);
+  g_assert_cmpint (err, ==, 0);
+
+  f->font = hb_ft_font_create (f->ft_face, NULL);
+
+  g_free (font_file);
+}
+
+static void
+ft_fixture_finish (ft_fixture_t *f, gconstpointer user_data)
+{
+  hb_font_destroy (f->font);
+
+  FT_Done_Face (f->ft_face);
+  FT_Done_FreeType (f->ft_library);
+}
+
+static void
+test_shape_complex (ft_fixture_t *f, gconstpointer user_data)
+{
+  const test_t *test = user_data;
+  const test_data_t *data = test->test_data;
+  hb_buffer_t *buffer;
+  unsigned int i, len, expected_len;
+  hb_glyph_info_t *glyphs;
+  hb_glyph_position_t *pos;
+  hb_bool_t fail;
+  GString *str;
+
+  g_assert (f->font);
+
+  if (data->comments[0])
+    g_test_message ("Test comments: %s", data->comments);
+
+  buffer =  hb_buffer_create ();
+  for (len = 0; data->characters[len]; len++) ;
+  hb_buffer_add_utf32 (buffer, data->characters, len, 0, len);
+
+  hb_shape (f->font, buffer, NULL, 0);
+
+  for (len = 0; data->glyphs[len]; len++) ;
+  expected_len = len;
+
+  glyphs = hb_buffer_get_glyph_infos (buffer, &len);
+  pos = hb_buffer_get_glyph_positions (buffer, NULL);
+  fail = len != expected_len;
+  if (!fail)
+    for (i = 0; i < len; i++)
+      if (glyphs[i].codepoint != data->glyphs[i]) {
+        fail = TRUE;
+	break;
+      }
+
+  str = g_string_new ("");
+  for (i = 0; i < expected_len; i++)
+    g_string_append_printf (str, " %4d", data->glyphs[i]);
+  g_test_message ("Expected glyphs: %s", str->str);
+  g_string_truncate (str, 0);
+  for (i = 0; i < len; i++)
+    g_string_append_printf (str, " %4d", glyphs[i].codepoint);
+  g_test_message ("Received glyphs: %s", str->str);
+  g_string_truncate (str, 0);
+  for (i = 0; i < len; i++)
+    g_string_append_printf (str, " %d", glyphs[i].cluster);
+  g_test_message ("Received clusters: %s", str->str);
+  g_string_truncate (str, 0);
+  for (i = 0; i < len; i++)
+    g_string_append_printf (str, " (%d,%d)+(%d,%d)",
+			    pos[i].x_offset,
+			    pos[i].y_offset,
+			    pos[i].x_advance,
+			    pos[i].y_advance);
+  g_test_message ("Received positions: %s", str->str);
+  g_string_free (str, TRUE);
+
+  if (fail) {
+    g_test_message ("FAIL");
+    /* The glib test framework is useless, lets not fail for now,
+     * we can grep for FAIL/PASS and count manually.  Sigh... */
+    /*g_test_fail ();*/
+  } else
+    g_test_message ("PASS");
+
+  hb_buffer_destroy (buffer);
+}
+
+static void
+test_shape_complex_skipped (gconstpointer user_data)
+{
+  const test_t *test = user_data;
+  const test_data_t *data = test->test_data;
+
+  if (data->comments[0])
+    g_test_message ("Test comments: %s", data->comments);
+
+  g_test_message ("Skipping test");
+}
+
+
+static void
+add_test_set (const test_set_t *test_set, const char *set_name)
+{
+  const test_data_t *data;
+  char *font_file;
+  hb_bool_t skip;
+
+  font_file = get_font_file (&test_set->font_data);
+  skip = !g_file_test (font_file, G_FILE_TEST_EXISTS);
+  g_free (font_file);
+
+  for (data = test_set->test_datas; data->characters[0]; data++) {
+    char *flavor;
+    GString *str;
+    const hb_codepoint_t *p;
+
+    test_t *test = g_slice_new0 (test_t);
+    test->font_data = &test_set->font_data;
+    test->test_data = data;
+
+    str = g_string_new ("<");
+    for (p = data->characters; *p; p++)
+      g_string_append_printf (str, "%04X,", *p);
+    str->str[str->len - 1] = '>';
+
+    flavor = g_strdup_printf ("%s/%s/%ld:%s", set_name, test_set->font_data.font_file, data - test_set->test_datas, str->str);
+
+    g_string_free (str, TRUE);
+
+    if (skip)
+      hb_test_add_data_flavor ((const void *) test, flavor, test_shape_complex_skipped);
+    else
+      hb_test_add_fixture_flavor (ft_fixture, (const void *) test, flavor, test_shape_complex);
+
+    g_free (flavor);
+  }
+}
+
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+#define TEST_SET(name) add_test_set (&tests_##name, #name)
+
+  TEST_SET (devanagari1);
+  TEST_SET (devanagari2);
+  TEST_SET (devanagari3);
+  TEST_SET (bengali1);
+  TEST_SET (bengali2);
+  TEST_SET (bengali3);
+  TEST_SET (gurmukhi);
+  TEST_SET (oriya);
+  TEST_SET (tamil);
+  TEST_SET (telugu);
+  TEST_SET (kannada1);
+  TEST_SET (kannada2);
+  TEST_SET (malayalam1);
+  TEST_SET (malayalam2);
+  TEST_SET (sinhala);
+
+  TEST_SET (khmer);
+
+  TEST_SET (nko);
+  TEST_SET (linearb);
+
+  return hb_test_run();
+}
diff --git a/test/api/test-shape.c b/test/api/test-shape.c
new file mode 100644
index 0000000..ccf6eed
--- /dev/null
+++ b/test/api/test-shape.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-shape.h */
+
+/*
+ * This test provides a framework to test aspects of hb_shape() that are
+ * font-independent.  Please add tests for any feature that fits that
+ * description.
+ */
+
+/* TODO Make this test data-driven and add some real test data */
+/* TODO Test positions too. And test non-native direction.  Test commit 2e18c6dbdfb */
+
+
+static const char test_data[] = "test\0data";
+
+static hb_position_t
+glyph_h_advance_func (hb_font_t *font, void *font_data,
+		      hb_codepoint_t glyph,
+		      void *user_data)
+{
+  switch (glyph) {
+  case 1: return 10;
+  case 2: return 6;
+  case 3: return 5;
+  }
+  return 0;
+}
+
+static hb_bool_t
+glyph_func (hb_font_t *font, void *font_data,
+	    hb_codepoint_t unicode, hb_codepoint_t variant_selector,
+	    hb_codepoint_t *glyph,
+	    void *user_data)
+{
+  switch (unicode) {
+  case 'T': *glyph = 1; return TRUE;
+  case 'e': *glyph = 2; return TRUE;
+  case 's': *glyph = 3; return TRUE;
+  }
+  return FALSE;
+}
+
+static hb_position_t
+glyph_h_kerning_func (hb_font_t *font, void *font_data,
+		      hb_codepoint_t left, hb_codepoint_t right,
+		      void *user_data)
+{
+  if (left == 1 && right == 2)
+    return -2;
+
+  return 0;
+}
+
+static const char TesT[] = "TesT";
+
+static void
+test_shape (void)
+{
+  hb_blob_t *blob;
+  hb_face_t *face;
+  hb_font_funcs_t *ffuncs;
+  hb_font_t *font;
+  hb_buffer_t *buffer;
+  unsigned int len;
+  hb_glyph_info_t *glyphs;
+  hb_glyph_position_t *positions;
+
+  blob = hb_blob_create (test_data, sizeof (test_data), HB_MEMORY_MODE_READONLY, NULL, NULL);
+  face = hb_face_create (blob, 0);
+  hb_blob_destroy (blob);
+  font = hb_font_create (face);
+  hb_face_destroy (face);
+  hb_font_set_scale (font, 10, 10);
+
+  ffuncs = hb_font_funcs_create ();
+  hb_font_funcs_set_glyph_h_advance_func (ffuncs, glyph_h_advance_func, NULL, NULL);
+  hb_font_funcs_set_glyph_func (ffuncs, glyph_func, NULL, NULL);
+  hb_font_funcs_set_glyph_h_kerning_func (ffuncs, glyph_h_kerning_func, NULL, NULL);
+  hb_font_set_funcs (font, ffuncs, NULL, NULL);
+  hb_font_funcs_destroy (ffuncs);
+
+  buffer =  hb_buffer_create ();
+  hb_buffer_set_direction (buffer, HB_DIRECTION_LTR);
+  hb_buffer_add_utf8 (buffer, TesT, 4, 0, 4);
+
+  hb_shape (font, buffer, NULL, 0);
+
+  len = hb_buffer_get_length (buffer);
+  glyphs = hb_buffer_get_glyph_infos (buffer, NULL);
+  positions = hb_buffer_get_glyph_positions (buffer, NULL);
+
+  {
+    const hb_codepoint_t output_glyphs[] = {1, 2, 3, 1};
+    const hb_position_t output_x_advances[] = {9, 5, 5, 10};
+    const hb_position_t output_x_offsets[] = {0, -1, 0, 0};
+    unsigned int i;
+    g_assert_cmpint (len, ==, 4);
+    for (i = 0; i < len; i++) {
+      g_assert_cmphex (glyphs[i].codepoint, ==, output_glyphs[i]);
+      g_assert_cmphex (glyphs[i].cluster,   ==, i);
+    }
+    for (i = 0; i < len; i++) {
+      g_assert_cmpint (output_x_advances[i], ==, positions[i].x_advance);
+      g_assert_cmpint (output_x_offsets [i], ==, positions[i].x_offset);
+      g_assert_cmpint (0, ==, positions[i].y_advance);
+      g_assert_cmpint (0, ==, positions[i].y_offset);
+    }
+  }
+
+  hb_buffer_destroy (buffer);
+  hb_font_destroy (font);
+}
+
+static void
+test_shape_list (void)
+{
+  const char **shapers = hb_shape_list_shapers ();
+
+  unsigned int i;
+  for (i = 0; shapers[i]; i++)
+    ;
+
+  g_assert_cmpint (i, >, 1);
+  g_assert (!strcmp (shapers[i - 1], "fallback"));
+}
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_shape);
+  /* TODO test fallback shaper */
+  /* TODO test shaper_full */
+  hb_test_add (test_shape_list);
+
+  return hb_test_run();
+}
diff --git a/test/api/test-unicode.c b/test/api/test-unicode.c
new file mode 100644
index 0000000..a420bf3
--- /dev/null
+++ b/test/api/test-unicode.c
@@ -0,0 +1,887 @@
+/*
+ * Copyright © 2011  Codethink Limited
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Codethink Author(s): Ryan Lortie
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-unicode.h */
+/* Unit tests for hb-glib.h */
+/* Unit tests for hb-icu.h */
+
+
+#ifdef HAVE_GLIB
+#include <hb-glib.h>
+#endif
+#ifdef HAVE_ICU
+#include <hb-icu.h>
+#endif
+
+
+/* Some useful stuff */
+
+#define MAGIC0 0x12345678
+#define MAGIC1 0x76543210
+
+typedef struct {
+  int value;
+  gboolean freed;
+} data_t;
+
+static void free_up (void *p)
+{
+  data_t *data = (data_t *) p;
+
+  g_assert (data->value == MAGIC0 || data->value == MAGIC1);
+  g_assert (!data->freed);
+  data->freed = TRUE;
+}
+
+static hb_script_t
+simple_get_script (hb_unicode_funcs_t *ufuncs,
+                   hb_codepoint_t      codepoint,
+                   void               *user_data)
+{
+  data_t *data = (data_t *) user_data;
+
+  g_assert (hb_unicode_funcs_get_parent (ufuncs) != NULL);
+  g_assert_cmphex (data->value, ==, MAGIC0);
+  g_assert (!data->freed);
+
+  if ('a' <= codepoint && codepoint <= 'z')
+    return HB_SCRIPT_LATIN;
+  else
+    return HB_SCRIPT_UNKNOWN;
+}
+
+static hb_script_t
+a_is_for_arabic_get_script (hb_unicode_funcs_t *ufuncs,
+                            hb_codepoint_t      codepoint,
+                            void               *user_data)
+{
+  data_t *data = (data_t *) user_data;
+
+  g_assert (hb_unicode_funcs_get_parent (ufuncs) != NULL);
+  g_assert_cmphex (data->value, ==, MAGIC1);
+  g_assert (!data->freed);
+
+  if (codepoint == 'a') {
+    return HB_SCRIPT_ARABIC;
+  } else {
+    hb_unicode_funcs_t *parent = hb_unicode_funcs_get_parent (ufuncs);
+
+    return hb_unicode_script (parent, codepoint);
+  }
+}
+
+
+
+/* Check all properties */
+
+/* Some of the following tables where adapted from glib/glib/tests/utf8-misc.c.
+ * The license is compatible. */
+
+typedef struct {
+  hb_codepoint_t unicode;
+  unsigned int   value;
+} test_pair_t;
+
+static const test_pair_t combining_class_tests[] =
+{
+  {   0x0020, 0 },
+  {   0x0334, 1 },
+  {   0x093C, 7 },
+  {   0x3099, 8 },
+  {   0x094D, 9 },
+  {   0x05B0, 10 },
+  {   0x05B1, 11 },
+  {   0x05B2, 12 },
+  {   0x05B3, 13 },
+  {   0x05B4, 14 },
+  {   0x05B5, 15 },
+  {   0x05B6, 16 },
+  {   0x05B7, 17 },
+  {   0x05B8, 18 },
+  {   0x05B9, 19 },
+  {   0x05BB, 20 },
+  {   0x05BC, 21 },
+  {   0x05BD, 22 },
+  {   0x05BF, 23 },
+  {   0x05C1, 24 },
+  {   0x05C2, 25 },
+  {   0xFB1E, 26 },
+  {   0x064B, 27 },
+  {   0x064C, 28 },
+  {   0x064D, 29 },
+  /* ... */
+  {   0x05AE, 228 },
+  {   0x0300, 230 },
+  {   0x302C, 232 },
+  {   0x0362, 233 },
+  {   0x0360, 234 },
+  {   0x0345, 240 },
+
+  { 0x111111, 0 }
+};
+static const test_pair_t combining_class_tests_more[] =
+{
+  /* Unicode-5.1 character additions */
+  {   0x1DCD, 234 },
+
+  /* Unicode-5.2 character additions */
+  {   0xA8E0, 230 },
+
+  /* Unicode-6.0 character additions */
+  {   0x135D, 230 },
+
+  { 0x111111, 0 }
+};
+
+static const test_pair_t eastasian_width_tests[] =
+{
+  /* Neutral */
+  {   0x0000, 1 },
+  {   0x0483, 1 },
+  {   0x0641, 1 },
+  {   0xFFFC, 1 },
+  {  0x10000, 1 },
+  {  0xE0001, 1 },
+
+  /* Narrow */
+  {   0x0020, 1 },
+  {   0x0041, 1 },
+  {   0x27E6, 1 },
+
+  /* Halfwidth */
+  {   0x20A9, 1 },
+  {   0xFF61, 1 },
+  {   0xFF69, 1 },
+  {   0xFFEE, 1 },
+
+  /* Ambiguous */
+  {   0x00A1, 1 },
+  {   0x00D8, 1 },
+  {   0x02DD, 1 },
+  {  0xE0100, 1 },
+  { 0x100000, 1 },
+
+  /* Fullwidth */
+  {   0x3000, 2 },
+  {   0xFF60, 2 },
+
+  /* Wide */
+  {   0x2329, 2 },
+  {   0x3001, 2 },
+  {   0xFE69, 2 },
+  {  0x30000, 2 },
+  {  0x3FFFD, 2 },
+
+  { 0x111111, 1 }
+};
+static const test_pair_t eastasian_width_tests_more[] =
+{
+  /* Default Wide blocks */
+  {   0x4DBF, 2 },
+  {   0x9FFF, 2 },
+  {   0xFAFF, 2 },
+  {  0x2A6DF, 2 },
+  {  0x2B73F, 2 },
+  {  0x2B81F, 2 },
+  {  0x2FA1F, 2 },
+
+  /* Uniode-5.2 character additions */
+  /* Wide */
+  {   0x115F, 2 },
+
+  /* Uniode-6.0 character additions */
+  /* Wide */
+  {  0x2B740, 2 },
+  {  0x1B000, 2 },
+
+  { 0x111111, 1 }
+};
+
+static const test_pair_t general_category_tests[] =
+{
+  {   0x000D, HB_UNICODE_GENERAL_CATEGORY_CONTROL },
+  {   0x200E, HB_UNICODE_GENERAL_CATEGORY_FORMAT },
+  {   0x0378, HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED },
+  {   0xE000, HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE },
+  {   0xD800, HB_UNICODE_GENERAL_CATEGORY_SURROGATE },
+  {   0x0061, HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER },
+  {   0x02B0, HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER },
+  {   0x3400, HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER },
+  {   0x01C5, HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER },
+  {   0xFF21, HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER },
+  {   0x0903, HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK },
+  {   0x20DD, HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK },
+  {   0xA806, HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK },
+  {   0xFF10, HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER },
+  {   0x16EE, HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER },
+  {   0x17F0, HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER },
+  {   0x005F, HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION },
+  {   0x058A, HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION },
+  {   0x0F3B, HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION },
+  {   0x2019, HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION },
+  {   0x2018, HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION },
+  {   0x2016, HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION },
+  {   0x0F3A, HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION },
+  {   0x20A0, HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL },
+  {   0x309B, HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL },
+  {   0xFB29, HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL },
+  {   0x00A6, HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL },
+  {   0x2028, HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR },
+  {   0x2029, HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR },
+  {   0x202F, HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR },
+
+  { 0x111111, HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED }
+};
+static const test_pair_t general_category_tests_more[] =
+{
+  /* Unicode-5.2 character additions */
+  {  0x1F131, HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL },
+
+  /* Unicode-6.0 character additions */
+  {   0x0620, HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER },
+
+  { 0x111111, HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED }
+};
+
+static const test_pair_t mirroring_tests[] =
+{
+  /* Some characters that do NOT mirror */
+  {   0x0020, 0x0020 },
+  {   0x0041, 0x0041 },
+  {   0x00F0, 0x00F0 },
+  {   0x27CC, 0x27CC },
+  {  0xE01EF, 0xE01EF },
+  {  0x1D7C3, 0x1D7C3 },
+  { 0x100000, 0x100000 },
+
+  /* Some characters that do mirror */
+  {   0x0029, 0x0028 },
+  {   0x0028, 0x0029 },
+  {   0x003E, 0x003C },
+  {   0x003C, 0x003E },
+  {   0x005D, 0x005B },
+  {   0x005B, 0x005D },
+  {   0x007D, 0x007B },
+  {   0x007B, 0x007D },
+  {   0x00BB, 0x00AB },
+  {   0x00AB, 0x00BB },
+  {   0x226B, 0x226A },
+  {   0x226A, 0x226B },
+  {   0x22F1, 0x22F0 },
+  {   0x22F0, 0x22F1 },
+  {   0xFF60, 0xFF5F },
+  {   0xFF5F, 0xFF60 },
+  {   0xFF63, 0xFF62 },
+  {   0xFF62, 0xFF63 },
+
+  { 0x111111, 0x111111 },
+};
+static const test_pair_t mirroring_tests_more[] =
+{
+  /* No new mirroring characters have been encoded in recent Unicode versions. */
+  { 0x111111, 0x111111 }
+};
+
+static const test_pair_t script_tests[] =
+{
+  {   0x002A, HB_SCRIPT_COMMON },
+  {   0x0670, HB_SCRIPT_INHERITED },
+  {   0x060D, HB_SCRIPT_ARABIC },
+  {   0x0559, HB_SCRIPT_ARMENIAN },
+  {   0x09CD, HB_SCRIPT_BENGALI },
+  {   0x31B6, HB_SCRIPT_BOPOMOFO },
+  {   0x13A2, HB_SCRIPT_CHEROKEE },
+  {   0x2CFD, HB_SCRIPT_COPTIC },
+  {   0x0482, HB_SCRIPT_CYRILLIC },
+  {  0x10401, HB_SCRIPT_DESERET },
+  {   0x094D, HB_SCRIPT_DEVANAGARI },
+  {   0x1258, HB_SCRIPT_ETHIOPIC },
+  {   0x10FC, HB_SCRIPT_GEORGIAN },
+  {  0x10341, HB_SCRIPT_GOTHIC },
+  {   0x0375, HB_SCRIPT_GREEK },
+  {   0x0A83, HB_SCRIPT_GUJARATI },
+  {   0x0A3C, HB_SCRIPT_GURMUKHI },
+  {   0x3005, HB_SCRIPT_HAN },
+  {   0x1100, HB_SCRIPT_HANGUL },
+  {   0x05BF, HB_SCRIPT_HEBREW },
+  {   0x309F, HB_SCRIPT_HIRAGANA },
+  {   0x0CBC, HB_SCRIPT_KANNADA },
+  {   0x30FF, HB_SCRIPT_KATAKANA },
+  {   0x17DD, HB_SCRIPT_KHMER },
+  {   0x0EDD, HB_SCRIPT_LAO },
+  {   0x0061, HB_SCRIPT_LATIN },
+  {   0x0D3D, HB_SCRIPT_MALAYALAM },
+  {   0x1843, HB_SCRIPT_MONGOLIAN },
+  {   0x1031, HB_SCRIPT_MYANMAR },
+  {   0x169C, HB_SCRIPT_OGHAM },
+  {  0x10322, HB_SCRIPT_OLD_ITALIC },
+  {   0x0B3C, HB_SCRIPT_ORIYA },
+  {   0x16EF, HB_SCRIPT_RUNIC },
+  {   0x0DBD, HB_SCRIPT_SINHALA },
+  {   0x0711, HB_SCRIPT_SYRIAC },
+  {   0x0B82, HB_SCRIPT_TAMIL },
+  {   0x0C03, HB_SCRIPT_TELUGU },
+  {   0x07B1, HB_SCRIPT_THAANA },
+  {   0x0E31, HB_SCRIPT_THAI },
+  {   0x0FD4, HB_SCRIPT_TIBETAN },
+  {   0x1401, HB_SCRIPT_CANADIAN_ABORIGINAL },
+  {   0xA015, HB_SCRIPT_YI },
+  {   0x1700, HB_SCRIPT_TAGALOG },
+  {   0x1720, HB_SCRIPT_HANUNOO },
+  {   0x1740, HB_SCRIPT_BUHID },
+  {   0x1760, HB_SCRIPT_TAGBANWA },
+
+  /* Unicode-4.0 additions */
+  {   0x2800, HB_SCRIPT_BRAILLE },
+  {  0x10808, HB_SCRIPT_CYPRIOT },
+  {   0x1932, HB_SCRIPT_LIMBU },
+  {  0x10480, HB_SCRIPT_OSMANYA },
+  {  0x10450, HB_SCRIPT_SHAVIAN },
+  {  0x10000, HB_SCRIPT_LINEAR_B },
+  {   0x1950, HB_SCRIPT_TAI_LE },
+  {  0x1039F, HB_SCRIPT_UGARITIC },
+
+  /* Unicode-4.1 additions */
+  {   0x1980, HB_SCRIPT_NEW_TAI_LUE },
+  {   0x1A1F, HB_SCRIPT_BUGINESE },
+  {   0x2C00, HB_SCRIPT_GLAGOLITIC },
+  {   0x2D6F, HB_SCRIPT_TIFINAGH },
+  {   0xA800, HB_SCRIPT_SYLOTI_NAGRI },
+  {  0x103D0, HB_SCRIPT_OLD_PERSIAN },
+  {  0x10A3F, HB_SCRIPT_KHAROSHTHI },
+
+  /* Unicode-5.0 additions */
+  {   0x0378, HB_SCRIPT_UNKNOWN },
+  {   0x1B04, HB_SCRIPT_BALINESE },
+  {  0x12000, HB_SCRIPT_CUNEIFORM },
+  {  0x10900, HB_SCRIPT_PHOENICIAN },
+  {   0xA840, HB_SCRIPT_PHAGS_PA },
+  {   0x07C0, HB_SCRIPT_NKO },
+
+  /* Unicode-5.1 additions */
+  {   0xA900, HB_SCRIPT_KAYAH_LI },
+  {   0x1C00, HB_SCRIPT_LEPCHA },
+  {   0xA930, HB_SCRIPT_REJANG },
+  {   0x1B80, HB_SCRIPT_SUNDANESE },
+  {   0xA880, HB_SCRIPT_SAURASHTRA },
+  {   0xAA00, HB_SCRIPT_CHAM },
+  {   0x1C50, HB_SCRIPT_OL_CHIKI },
+  {   0xA500, HB_SCRIPT_VAI },
+  {  0x102A0, HB_SCRIPT_CARIAN },
+  {  0x10280, HB_SCRIPT_LYCIAN },
+  {  0x1093F, HB_SCRIPT_LYDIAN },
+
+  { 0x111111, HB_SCRIPT_UNKNOWN }
+};
+static const test_pair_t script_tests_more[] =
+{
+  /* Unicode-5.2 additions */
+  {  0x10B00, HB_SCRIPT_AVESTAN },
+  {   0xA6A0, HB_SCRIPT_BAMUM },
+  {  0x13000, HB_SCRIPT_EGYPTIAN_HIEROGLYPHS },
+  {  0x10840, HB_SCRIPT_IMPERIAL_ARAMAIC },
+  {  0x10B60, HB_SCRIPT_INSCRIPTIONAL_PAHLAVI },
+  {  0x10B40, HB_SCRIPT_INSCRIPTIONAL_PARTHIAN },
+  {   0xA980, HB_SCRIPT_JAVANESE },
+  {  0x11082, HB_SCRIPT_KAITHI },
+  {   0xA4D0, HB_SCRIPT_LISU },
+  {   0xABE5, HB_SCRIPT_MEETEI_MAYEK },
+  {  0x10A60, HB_SCRIPT_OLD_SOUTH_ARABIAN },
+  {  0x10C00, HB_SCRIPT_OLD_TURKIC },
+  {   0x0800, HB_SCRIPT_SAMARITAN },
+  {   0x1A20, HB_SCRIPT_TAI_THAM },
+  {   0xAA80, HB_SCRIPT_TAI_VIET },
+
+  /* Unicode-6.0 additions */
+  {   0x1BC0, HB_SCRIPT_BATAK },
+  {  0x11000, HB_SCRIPT_BRAHMI },
+  {   0x0840, HB_SCRIPT_MANDAIC },
+
+  /* Unicode-5.2 character additions */
+  {   0x1CED, HB_SCRIPT_INHERITED },
+  {   0x1400, HB_SCRIPT_CANADIAN_ABORIGINAL },
+
+  { 0x111111, HB_SCRIPT_UNKNOWN }
+};
+
+
+typedef unsigned int (*get_func_t)         (hb_unicode_funcs_t *ufuncs,
+					    hb_codepoint_t      unicode,
+					    void               *user_data);
+typedef unsigned int (*func_setter_func_t) (hb_unicode_funcs_t *ufuncs,
+					    get_func_t          func,
+					    void               *user_data,
+					    hb_destroy_func_t   destroy);
+typedef unsigned int (*getter_func_t)      (hb_unicode_funcs_t *ufuncs,
+					    hb_codepoint_t      unicode);
+
+typedef struct {
+  const char         *name;
+  func_setter_func_t  func_setter;
+  getter_func_t       getter;
+  const test_pair_t  *tests;
+  unsigned int        num_tests;
+  const test_pair_t  *tests_more;
+  unsigned int        num_tests_more;
+  unsigned int        default_value;
+} property_t;
+
+#define RETURNS_UNICODE_ITSELF ((unsigned int) -1)
+
+#define PROPERTY(name, DEFAULT) \
+  { \
+    #name, \
+    (func_setter_func_t) hb_unicode_funcs_set_##name##_func, \
+    (getter_func_t) hb_unicode_##name, \
+    name##_tests, \
+    G_N_ELEMENTS (name##_tests), \
+    name##_tests_more, \
+    G_N_ELEMENTS (name##_tests_more), \
+    DEFAULT \
+  }
+static const property_t properties[] =
+{
+  PROPERTY (combining_class, 0),
+  PROPERTY (eastasian_width, 1),
+  PROPERTY (general_category, (unsigned int) HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER),
+  PROPERTY (mirroring, RETURNS_UNICODE_ITSELF),
+  PROPERTY (script, (unsigned int) HB_SCRIPT_UNKNOWN)
+};
+#undef PROPERTY
+
+static void
+test_unicode_properties (gconstpointer user_data)
+{
+  hb_unicode_funcs_t *uf = (hb_unicode_funcs_t *) user_data;
+  unsigned int i, j;
+  gboolean failed = TRUE;
+
+  g_assert (hb_unicode_funcs_is_immutable (uf));
+  g_assert (hb_unicode_funcs_get_parent (uf));
+
+  for (i = 0; i < G_N_ELEMENTS (properties); i++) {
+    const property_t *p = &properties[i];
+    const test_pair_t *tests;
+
+    g_test_message ("Testing property %s", p->name);
+    tests = p->tests;
+    for (j = 0; j < p->num_tests; j++) {
+      g_test_message ("Test %s #%d: U+%04X", p->name, j, tests[j].unicode);
+      g_assert_cmphex (p->getter (uf, tests[j].unicode), ==, tests[j].value);
+    }
+    /* These tests are from Unicode 5.2 onward and older glib/ICU
+     * don't get them right.  Just warn instead of assert. */
+    tests = p->tests_more;
+    for (j = 0; j < p->num_tests_more; j++) {
+      g_test_message ("Test %s more #%d: U+%04X", p->name, j, tests[j].unicode);
+      if (p->getter (uf, tests[j].unicode) != tests[j].value) {
+	g_test_message ("Soft fail: Received %x, expected %x", p->getter (uf, tests[j].unicode), tests[j].value);
+        failed = TRUE;
+      }
+    }
+  }
+
+  if (failed)
+    g_test_message ("Some property tests failed.  You probably have an old version of one of the libraries used.");
+}
+
+static hb_codepoint_t
+default_value (hb_codepoint_t _default_value, hb_codepoint_t unicode)
+{
+  return _default_value == RETURNS_UNICODE_ITSELF ?  unicode : _default_value;
+}
+
+static void
+_test_unicode_properties_nil (hb_unicode_funcs_t *uf)
+{
+  unsigned int i, j;
+
+  for (i = 0; i < G_N_ELEMENTS (properties); i++) {
+    const property_t *p = &properties[i];
+    const test_pair_t *tests;
+
+    g_test_message ("Testing property %s", p->name);
+    tests = p->tests;
+    for (j = 0; j < p->num_tests; j++) {
+      g_test_message ("Test %s #%d: U+%04X", p->name, j, tests[j].unicode);
+      g_assert_cmphex (p->getter (uf, tests[j].unicode), ==, default_value (p->default_value, tests[j].unicode));
+    }
+    tests = p->tests_more;
+    for (j = 0; j < p->num_tests_more; j++) {
+      g_test_message ("Test %s more #%d: U+%04X", p->name, j, tests[j].unicode);
+      g_assert_cmphex (p->getter (uf, tests[j].unicode), ==, default_value (p->default_value, tests[j].unicode));
+    }
+  }
+}
+
+static void
+test_unicode_properties_nil (void)
+{
+  hb_unicode_funcs_t *uf = hb_unicode_funcs_create (NULL);
+
+  g_assert (!hb_unicode_funcs_is_immutable (uf));
+  _test_unicode_properties_nil (uf);
+
+  hb_unicode_funcs_destroy (uf);
+}
+
+static void
+test_unicode_properties_empty (void)
+{
+  hb_unicode_funcs_t *uf = hb_unicode_funcs_get_empty ();
+
+  g_assert (uf);
+  g_assert (hb_unicode_funcs_is_immutable (uf));
+  _test_unicode_properties_nil (uf);
+}
+
+
+static void
+test_unicode_chainup (void)
+{
+  hb_unicode_funcs_t *uf, *uf2;
+
+  /* Chain-up to nil */
+
+  uf = hb_unicode_funcs_create (NULL);
+  g_assert (!hb_unicode_funcs_is_immutable (uf));
+
+  uf2 = hb_unicode_funcs_create (uf);
+  g_assert (hb_unicode_funcs_is_immutable (uf));
+  hb_unicode_funcs_destroy (uf);
+
+  g_assert (!hb_unicode_funcs_is_immutable (uf2));
+  _test_unicode_properties_nil (uf2);
+
+  hb_unicode_funcs_destroy (uf2);
+
+  /* Chain-up to default */
+
+  uf = hb_unicode_funcs_create (hb_unicode_funcs_get_default ());
+  g_assert (!hb_unicode_funcs_is_immutable (uf));
+
+  uf2 = hb_unicode_funcs_create (uf);
+  g_assert (hb_unicode_funcs_is_immutable (uf));
+  hb_unicode_funcs_destroy (uf);
+
+  g_assert (!hb_unicode_funcs_is_immutable (uf2));
+  hb_unicode_funcs_make_immutable (uf2);
+  test_unicode_properties (uf2);
+
+  hb_unicode_funcs_destroy (uf2);
+
+}
+
+static void
+test_unicode_setters (void)
+{
+  hb_unicode_funcs_t *uf;
+  unsigned int i;
+
+  /* This is cruel: we use script-returning functions to test all properties,
+   * but it works. */
+
+  for (i = 0; i < G_N_ELEMENTS (properties); i++) {
+    const property_t *p = &properties[i];
+    data_t data[2] = {{MAGIC0, FALSE}, {MAGIC1, FALSE}};
+
+    g_test_message ("Testing property %s", p->name);
+
+    uf = hb_unicode_funcs_create (NULL);
+    g_assert (!hb_unicode_funcs_is_immutable (uf));
+
+    p->func_setter (uf, (get_func_t) simple_get_script, &data[0], free_up);
+
+    g_assert_cmphex (p->getter (uf, 'a'), ==, HB_SCRIPT_LATIN);
+    g_assert_cmphex (p->getter (uf, '0'), ==, HB_SCRIPT_UNKNOWN);
+
+    p->func_setter (uf, (get_func_t) NULL, NULL, NULL);
+    g_assert (data[0].freed && !data[1].freed);
+
+    g_assert (!hb_unicode_funcs_is_immutable (uf));
+    hb_unicode_funcs_make_immutable (uf);
+    g_assert (hb_unicode_funcs_is_immutable (uf));
+
+    /* Since uf is immutable now, the following setter should do nothing. */
+    p->func_setter (uf, (get_func_t) a_is_for_arabic_get_script, &data[1], free_up);
+
+    g_assert (data[0].freed && !data[1].freed);
+    hb_unicode_funcs_destroy (uf);
+    g_assert (data[0].freed && !data[1].freed);
+  }
+}
+
+
+
+typedef struct {
+  data_t data[2];
+} data_fixture_t;
+
+static void
+data_fixture_init (data_fixture_t *f, gconstpointer user_data)
+{
+  f->data[0].value = MAGIC0;
+  f->data[1].value = MAGIC1;
+}
+static void
+data_fixture_finish (data_fixture_t *f, gconstpointer user_data)
+{
+}
+
+static void
+test_unicode_subclassing_nil (data_fixture_t *f, gconstpointer user_data)
+{
+  hb_unicode_funcs_t *uf, *aa;
+
+  uf = hb_unicode_funcs_create (NULL);
+
+  aa = hb_unicode_funcs_create (uf);
+
+  hb_unicode_funcs_destroy (uf);
+
+  hb_unicode_funcs_set_script_func (aa, a_is_for_arabic_get_script,
+                                    &f->data[1], free_up);
+
+  g_assert_cmphex (hb_unicode_script (aa, 'a'), ==, HB_SCRIPT_ARABIC);
+  g_assert_cmphex (hb_unicode_script (aa, 'b'), ==, HB_SCRIPT_UNKNOWN);
+
+  g_assert (!f->data[0].freed && !f->data[1].freed);
+  hb_unicode_funcs_destroy (aa);
+  g_assert (!f->data[0].freed && f->data[1].freed);
+}
+
+static void
+test_unicode_subclassing_default (data_fixture_t *f, gconstpointer user_data)
+{
+  hb_unicode_funcs_t *uf, *aa;
+
+  uf = hb_unicode_funcs_get_default ();
+  aa = hb_unicode_funcs_create (uf);
+
+  hb_unicode_funcs_set_script_func (aa, a_is_for_arabic_get_script,
+                                    &f->data[1], free_up);
+
+  g_assert_cmphex (hb_unicode_script (aa, 'a'), ==, HB_SCRIPT_ARABIC);
+  g_assert_cmphex (hb_unicode_script (aa, 'b'), ==, HB_SCRIPT_LATIN);
+
+  g_assert (!f->data[0].freed && !f->data[1].freed);
+  hb_unicode_funcs_destroy (aa);
+  g_assert (!f->data[0].freed && f->data[1].freed);
+}
+
+static void
+test_unicode_subclassing_deep (data_fixture_t *f, gconstpointer user_data)
+{
+  hb_unicode_funcs_t *uf, *aa;
+
+  uf = hb_unicode_funcs_create (NULL);
+
+  hb_unicode_funcs_set_script_func (uf, simple_get_script,
+                                    &f->data[0], free_up);
+
+  aa = hb_unicode_funcs_create (uf);
+
+  hb_unicode_funcs_destroy (uf);
+
+  /* make sure the 'uf' didn't get freed, since 'aa' holds a ref */
+  g_assert (!f->data[0].freed);
+
+  hb_unicode_funcs_set_script_func (aa, a_is_for_arabic_get_script,
+                                    &f->data[1], free_up);
+
+  g_assert_cmphex (hb_unicode_script (aa, 'a'), ==, HB_SCRIPT_ARABIC);
+  g_assert_cmphex (hb_unicode_script (aa, 'b'), ==, HB_SCRIPT_LATIN);
+  g_assert_cmphex (hb_unicode_script (aa, '0'), ==, HB_SCRIPT_UNKNOWN);
+
+  g_assert (!f->data[0].freed && !f->data[1].freed);
+  hb_unicode_funcs_destroy (aa);
+  g_assert (f->data[0].freed && f->data[1].freed);
+}
+
+
+static hb_script_t
+script_roundtrip_default (hb_script_t script)
+{
+  return hb_script_from_iso15924_tag (hb_script_to_iso15924_tag (script));
+}
+
+#ifdef HAVE_GLIB
+static hb_script_t
+script_roundtrip_glib (hb_script_t script)
+{
+  return hb_glib_script_to_script (hb_glib_script_from_script (script));
+}
+#endif
+
+#ifdef HAVE_ICU
+static hb_script_t
+script_roundtrip_icu (hb_script_t script)
+{
+  return hb_icu_script_to_script (hb_icu_script_from_script (script));
+}
+#endif
+
+static void
+test_unicode_script_roundtrip (gconstpointer user_data)
+{
+  typedef hb_script_t (*roundtrip_func_t) (hb_script_t);
+  roundtrip_func_t roundtrip_func = (roundtrip_func_t) user_data;
+  unsigned int i;
+  gboolean failed = FALSE;
+
+  for (i = 0; i < G_N_ELEMENTS (script_tests); i++) {
+    const test_pair_t *test = &script_tests[i];
+    hb_script_t script = test->value;
+
+    g_test_message ("Test script roundtrip #%d: %x", i, script);
+    g_assert_cmphex (script, ==, roundtrip_func (script));
+  }
+  for (i = 0; i < G_N_ELEMENTS (script_tests_more); i++) {
+    const test_pair_t *test = &script_tests_more[i];
+    hb_script_t script = test->value;
+
+    g_test_message ("Test script roundtrip more #%d: %x", i, script);
+    if (script != roundtrip_func (script)) {
+      g_test_message ("Soft fail: Received %x, expected %x", roundtrip_func (script), script);
+      failed = TRUE;
+    }
+  }
+
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, roundtrip_func (HB_SCRIPT_INVALID));
+
+  if (failed)
+    g_test_message ("Some script roundtrip tests failed.  You probably have an old version of one of the libraries used.");
+}
+
+
+static void
+test_unicode_normalization (gconstpointer user_data)
+{
+  hb_unicode_funcs_t *uf = (hb_unicode_funcs_t *) user_data;
+  gunichar a, b, ab;
+
+
+  /* Test compose() */
+
+  /* Not composable */
+  g_assert (!hb_unicode_compose (uf, 0x0041, 0x0042, &ab) && ab == 0);
+  g_assert (!hb_unicode_compose (uf, 0x0041, 0, &ab) && ab == 0);
+  g_assert (!hb_unicode_compose (uf, 0x0066, 0x0069, &ab) && ab == 0);
+
+  /* Singletons should not compose */
+  g_assert (!hb_unicode_compose (uf, 0x212B, 0, &ab) && ab == 0);
+  g_assert (!hb_unicode_compose (uf, 0x00C5, 0, &ab) && ab == 0);
+  g_assert (!hb_unicode_compose (uf, 0x2126, 0, &ab) && ab == 0);
+  g_assert (!hb_unicode_compose (uf, 0x03A9, 0, &ab) && ab == 0);
+
+  /* Non-starter pairs should not compose */
+  g_assert (!hb_unicode_compose (uf, 0x0308, 0x0301, &ab) && ab == 0); /* !0x0344 */
+  g_assert (!hb_unicode_compose (uf, 0x0F71, 0x0F72, &ab) && ab == 0); /* !0x0F73 */
+
+  /* Pairs */
+  g_assert (hb_unicode_compose (uf, 0x0041, 0x030A, &ab) && ab == 0x00C5);
+  g_assert (hb_unicode_compose (uf, 0x006F, 0x0302, &ab) && ab == 0x00F4);
+  g_assert (hb_unicode_compose (uf, 0x1E63, 0x0307, &ab) && ab == 0x1E69);
+  g_assert (hb_unicode_compose (uf, 0x0073, 0x0323, &ab) && ab == 0x1E63);
+  g_assert (hb_unicode_compose (uf, 0x0064, 0x0307, &ab) && ab == 0x1E0B);
+  g_assert (hb_unicode_compose (uf, 0x0064, 0x0323, &ab) && ab == 0x1E0D);
+
+  /* Hangul */
+  g_assert (hb_unicode_compose (uf, 0xD4CC, 0x11B6, &ab) && ab == 0xD4DB);
+  g_assert (hb_unicode_compose (uf, 0x1111, 0x1171, &ab) && ab == 0xD4CC);
+  g_assert (hb_unicode_compose (uf, 0xCE20, 0x11B8, &ab) && ab == 0xCE31);
+  g_assert (hb_unicode_compose (uf, 0x110E, 0x1173, &ab) && ab == 0xCE20);
+
+
+  /* Test decompose() */
+
+  /* Not decomposable */
+  g_assert (!hb_unicode_decompose (uf, 0x0041, &a, &b) && a == 0x0041 && b == 0);
+  g_assert (!hb_unicode_decompose (uf, 0xFB01, &a, &b) && a == 0xFB01 && b == 0);
+  g_assert (!hb_unicode_decompose (uf, 0x1F1EF, &a, &b) && a == 0x1F1EF && b == 0);
+
+  /* Singletons */
+  g_assert (hb_unicode_decompose (uf, 0x212B, &a, &b) && a == 0x00C5 && b == 0);
+  g_assert (hb_unicode_decompose (uf, 0x2126, &a, &b) && a == 0x03A9 && b == 0);
+
+  /* Non-starter pairs decompose, but not compose */
+  g_assert (hb_unicode_decompose (uf, 0x0344, &a, &b) && a == 0x0308 && b == 0x0301);
+  g_assert (hb_unicode_decompose (uf, 0x0F73, &a, &b) && a == 0x0F71 && b == 0x0F72);
+
+  /* Pairs */
+  g_assert (hb_unicode_decompose (uf, 0x00C5, &a, &b) && a == 0x0041 && b == 0x030A);
+  g_assert (hb_unicode_decompose (uf, 0x00F4, &a, &b) && a == 0x006F && b == 0x0302);
+  g_assert (hb_unicode_decompose (uf, 0x1E69, &a, &b) && a == 0x1E63 && b == 0x0307);
+  g_assert (hb_unicode_decompose (uf, 0x1E63, &a, &b) && a == 0x0073 && b == 0x0323);
+  g_assert (hb_unicode_decompose (uf, 0x1E0B, &a, &b) && a == 0x0064 && b == 0x0307);
+  g_assert (hb_unicode_decompose (uf, 0x1E0D, &a, &b) && a == 0x0064 && b == 0x0323);
+
+  /* Hangul */
+  g_assert (hb_unicode_decompose (uf, 0xD4DB, &a, &b) && a == 0xD4CC && b == 0x11B6);
+  g_assert (hb_unicode_decompose (uf, 0xD4CC, &a, &b) && a == 0x1111 && b == 0x1171);
+  g_assert (hb_unicode_decompose (uf, 0xCE31, &a, &b) && a == 0xCE20 && b == 0x11B8);
+  g_assert (hb_unicode_decompose (uf, 0xCE20, &a, &b) && a == 0x110E && b == 0x1173);
+
+}
+
+
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_unicode_properties_nil);
+  hb_test_add (test_unicode_properties_empty);
+
+  hb_test_add_data_flavor (hb_unicode_funcs_get_default (),          "default", test_unicode_properties);
+  hb_test_add_data_flavor (hb_unicode_funcs_get_default (),          "default", test_unicode_normalization);
+  hb_test_add_data_flavor ((gconstpointer) script_roundtrip_default, "default", test_unicode_script_roundtrip);
+#ifdef HAVE_GLIB
+  hb_test_add_data_flavor (hb_glib_get_unicode_funcs (),             "glib",    test_unicode_properties);
+  hb_test_add_data_flavor (hb_glib_get_unicode_funcs (),             "glib",    test_unicode_normalization);
+  hb_test_add_data_flavor ((gconstpointer) script_roundtrip_glib,    "glib",    test_unicode_script_roundtrip);
+#endif
+#ifdef HAVE_ICU
+  hb_test_add_data_flavor (hb_icu_get_unicode_funcs (),              "icu",     test_unicode_properties);
+  hb_test_add_data_flavor (hb_icu_get_unicode_funcs (),              "icu",     test_unicode_normalization);
+  hb_test_add_data_flavor ((gconstpointer) script_roundtrip_icu,     "icu",     test_unicode_script_roundtrip);
+#endif
+
+  hb_test_add (test_unicode_chainup);
+
+  hb_test_add (test_unicode_setters);
+
+  hb_test_add_fixture (data_fixture, NULL, test_unicode_subclassing_nil);
+  hb_test_add_fixture (data_fixture, NULL, test_unicode_subclassing_default);
+  hb_test_add_fixture (data_fixture, NULL, test_unicode_subclassing_deep);
+
+  return hb_test_run ();
+}
diff --git a/test/api/test-version.c b/test/api/test-version.c
new file mode 100644
index 0000000..4c9bd37
--- /dev/null
+++ b/test/api/test-version.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "hb-test.h"
+
+/* Unit tests for hb-version.h */
+
+
+static void
+test_version (void)
+{
+  unsigned int major, minor, micro;
+  char *s;
+
+  hb_version (&major, &minor, &micro);
+
+  g_assert_cmpint (major, ==, HB_VERSION_MAJOR);
+  g_assert_cmpint (minor, ==, HB_VERSION_MINOR);
+  g_assert_cmpint (micro, ==, HB_VERSION_MICRO);
+
+  s = g_strdup_printf ("%u.%u.%u", major, minor, micro);
+  g_assert (0 == strcmp (HB_VERSION_STRING, s));
+  g_free (s);
+  g_assert (0 == strcmp (HB_VERSION_STRING, hb_version_string ()));
+
+  g_assert (HB_VERSION_CHECK (major, minor, micro));
+  g_assert (HB_VERSION_CHECK (major+1, minor, micro));
+  g_assert (HB_VERSION_CHECK (major, minor+1, micro));
+  g_assert (HB_VERSION_CHECK (major, minor, micro+1));
+  if (major)
+    g_assert (!HB_VERSION_CHECK (major-1, minor, micro));
+  if (minor)
+    g_assert (!HB_VERSION_CHECK (major, minor-1, micro));
+  if (micro)
+    g_assert (!HB_VERSION_CHECK (major, minor, micro-1));
+
+  g_assert (hb_version_check (major, minor, micro));
+  g_assert (hb_version_check (major+1, minor, micro));
+  g_assert (hb_version_check (major, minor+1, micro));
+  g_assert (hb_version_check (major, minor, micro+1));
+  if (major)
+    g_assert (!hb_version_check (major-1, minor, micro));
+  if (minor)
+    g_assert (!hb_version_check (major, minor-1, micro));
+  if (micro)
+    g_assert (!hb_version_check (major, minor, micro-1));
+}
+
+int
+main (int argc, char **argv)
+{
+  hb_test_init (&argc, &argv);
+
+  hb_test_add (test_version);
+
+  return hb_test_run();
+}