=== modified file 'tests/check/elements/cmmldec.c'
--- tests/check/elements/cmmldec.c	2006-07-29 14:32:26 +0000
+++ tests/check/elements/cmmldec.c	2006-08-07 20:48:18 +0000
@@ -22,7 +22,6 @@
  */
 
 #include <gst/check/gstcheck.h>
-
 #include <gst/tag/tag.h>
 
 #define SINK_CAPS "text/x-cmml"
@@ -34,16 +33,13 @@
   "\xe8\x03\x00\x00\x00\x00\x00\x00"\
   "\x01\x00\x00\x00\x00\x00\x00\x00"\
   "\x20"
+#define IDENT_HEADER_SIZE 29
 
-#define XML_PREAMBLE \
+#define PREAMBLE_NO_PI \
   "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"\
-  "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n"\
-
-#define PREAMBLE \
- XML_PREAMBLE "<?cmml?>"
-
-#define PREAMBLE_DECODED \
- XML_PREAMBLE "<cmml >"
+  "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n"
+#define PREAMBLE PREAMBLE_NO_PI "<?cmml?>"
+#define PREAMBLE_DECODED PREAMBLE_NO_PI "<cmml >"
 
 #define HEAD_TAG \
   "<head>"\
@@ -73,15 +69,31 @@
   "<meta name=\"test\" content=\"test content\"/>"\
   "</clip>"
 
+#define EMPTY_CLIP_TEMPLATE \
+  "<clip id=\"%s\" track=\"%s\" />"
+
 #define END_TAG \
   "</cmml>"
 
+#define fail_unless_equals_flow_return(a, b)                            \
+G_STMT_START {                                                          \
+  gchar *a_up = g_ascii_strup (gst_flow_get_name (a), -1);              \
+  gchar *b_up = g_ascii_strup (gst_flow_get_name (b), -1);              \
+  fail_unless (a == b,                                                  \
+      "'" #a "' (GST_FLOW_%s) is not equal to '" #b "' (GST_FLOW_%s)",  \
+      a_up, b_up);                                                      \
+  g_free (a_up);                                                        \
+  g_free (b_up);                                                        \
+} G_STMT_END;
+
+static GstElement *cmmldec;
+static GstBus *bus;
+static GstFlowReturn flow;
 GList *buffers;
-GList *current_buf = NULL;
-gint64 granulerate;
-guint8 granuleshift;
-
-GstPad *srcpad, *sinkpad;
+static GList *current_buf;
+static gint64 granulerate;
+static guint8 granuleshift;
+static GstPad *srcpad, *sinkpad;
 
 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -117,12 +129,9 @@
   gst_buffer_unref (GST_BUFFER (buffer));
 }
 
-GstElement *
+static void
 setup_cmmldec ()
 {
-  GstElement *cmmldec;
-  GstBus *bus;
-
   GST_DEBUG ("setup_cmmldec");
   cmmldec = gst_check_setup_element ("cmmldec");
   srcpad = gst_check_setup_src_pad (cmmldec, &srctemplate, NULL);
@@ -138,23 +147,20 @@
   granulerate = GST_SECOND / 1000;
   granuleshift = 32;
   buffers = NULL;
-
-  return cmmldec;
 }
 
 static void
-cleanup_cmmldec (GstElement * cmmldec)
+teardown_cmmldec ()
 {
-  GstBus *bus;
-
   g_list_foreach (buffers, buffer_unref, NULL);
   g_list_free (buffers);
+  buffers = NULL;
+  current_buf = NULL;
 
-  bus = GST_ELEMENT_BUS (cmmldec);
   gst_bus_set_flushing (bus, TRUE);
   gst_object_unref (bus);
 
-  GST_DEBUG ("cleanup_cmmldec");
+  GST_DEBUG ("teardown_cmmldec");
   gst_check_teardown_src_pad (cmmldec);
   gst_check_teardown_sink_pad (cmmldec);
   gst_check_teardown_element (cmmldec);
@@ -164,71 +170,146 @@
 check_output_buffer_is_equal (const gchar * name,
     const gchar * data, gint refcount)
 {
-  GstBuffer *buffer = GST_BUFFER (current_buf->data);
+  GstBuffer *buffer;
+
+  if (current_buf == NULL)
+    current_buf = buffers;
+  else
+    current_buf = g_list_next (current_buf);
+
+  fail_unless (current_buf != NULL);
+
+  buffer = GST_BUFFER (current_buf->data);
 
   ASSERT_OBJECT_REFCOUNT (buffer, name, refcount);
-  fail_unless (memcmp (GST_BUFFER_DATA (buffer), data,
-          GST_BUFFER_SIZE (buffer)) == 0,
+  fail_unless (memcmp (GST_BUFFER_DATA (buffer), data, 
+        GST_BUFFER_SIZE (buffer)) == 0,
       "'%s' (%s) is not equal to (%s)", name, GST_BUFFER_DATA (buffer), data);
 }
 
-static void
+static GstFlowReturn
 push_data (const gchar * name,
-    const gchar * data, gint size, gint64 granulepos,
-    GstFlowReturn expected_return)
+    const gchar * data, gint size, gint64 granulepos)
 {
   GstBuffer *buffer;
-  GstFlowReturn res;
 
   buffer = buffer_new (data, size);
   GST_BUFFER_OFFSET_END (buffer) = granulepos;
-  res = gst_pad_push (srcpad, buffer);
-  fail_unless (res == expected_return,
-      "pushing %s returned %d not %d", name, res, expected_return);
+  return gst_pad_push (srcpad, buffer);
+}
+
+static GObject *
+cmml_tag_message_pop (GstBus *bus, const gchar *tag)
+{
+  GstMessage *message;
+  GstTagList *taglist;
+  const GValue *value;
+  GObject *obj;
+
+  message = gst_bus_poll (bus, GST_MESSAGE_TAG, 0);
+  if (message == NULL)
+    return NULL;
+
+  gst_message_parse_tag (message, &taglist);
+  value = gst_tag_list_get_value_index (taglist, tag, 0);
+  if (value == NULL) {
+    gst_message_unref (message);
+    gst_tag_list_free (taglist);
+    return NULL;
+  }
+
+  obj = g_value_dup_object (value);
+  gst_message_unref (message);
+  gst_tag_list_free (taglist);
+
+  return obj;
 }
 
 static void
 check_headers ()
 {
+  GObject *head_tag;
+  gchar *title, *base;
+  GValueArray *meta;
+
   /* push the ident header */
-  push_data ("ident-header", IDENT_HEADER, 29, 0, GST_FLOW_OK);
-  /* push the cmml start tag */
-  push_data ("preamble", PREAMBLE, strlen (PREAMBLE), 0, GST_FLOW_OK);
+  flow = push_data ("ident-header", IDENT_HEADER, IDENT_HEADER_SIZE, 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
+  /* push the cmml preamble */
+  flow = push_data ("preamble", PREAMBLE, strlen (PREAMBLE), 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
   /* push the head tag */
-  push_data ("head", HEAD_TAG, strlen (HEAD_TAG), 0, GST_FLOW_OK);
-
-  current_buf = buffers;
-  fail_unless_equals_int (g_list_length (current_buf), 2);
-
-  /* check the preamble */
+  flow = push_data ("head", HEAD_TAG, strlen (HEAD_TAG), 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
+  fail_unless_equals_int (g_list_length (buffers), 2);
+
+  /* check the decoded preamble */
   check_output_buffer_is_equal ("cmml-preamble-buffer", PREAMBLE_DECODED, 1);
 
   /* check the decoded head tag */
-  current_buf = current_buf->next;
   check_output_buffer_is_equal ("head-tag-buffer", HEAD_TAG_DECODED, 1);
+
+  /* check the GstCmmlTagHead tag object */
+  head_tag = cmml_tag_message_pop (bus, GST_TAG_CMML_HEAD);
+  fail_unless (head_tag != NULL);
+  g_object_get (head_tag,
+      "title", &title, "base-uri", &base, "meta", &meta, NULL);
+  fail_unless_equals_string ("The Research Hunter", title);
+  fail_unless (base == NULL);
+  fail_unless (meta != NULL);
+  fail_unless_equals_int (meta->n_values, 10);
+
+  g_free (title);
+  g_free (base);
+  g_value_array_free (meta);
+  g_object_unref (head_tag);
 }
 
-static void
-push_clip (const gchar * name, const gchar * track, GstClockTime prev,
-    GstClockTime start, GstClockTime end, GstFlowReturn expected_return)
+static GstFlowReturn
+push_clip_full (const gchar * name, const gchar * track, const gchar *template,
+    GstClockTime prev, GstClockTime start)
 {
   gchar *clip;
   gint64 keyindex, keyoffset, granulepos;
+  GstFlowReturn res;
 
   if (track == NULL)
     track = "default";
 
+  if (prev == GST_CLOCK_TIME_NONE)
+    prev = 0;
+
   keyindex = prev / granulerate << granuleshift;
   keyoffset = (start - prev) / granulerate;
   granulepos = keyindex + keyoffset;
 
-  clip = g_strdup_printf (CLIP_TEMPLATE, name, track);
-  push_data (name, clip, strlen (clip), granulepos, expected_return);
+  clip = g_strdup_printf (template, name, track);
+  res = push_data (name, clip, strlen (clip), granulepos);
   g_free (clip);
-}
+
+  return res;
+}
+
+static GstFlowReturn
+push_clip (const gchar *name, const gchar *track,
+    GstClockTime prev, GstClockTime start)
+{
+  return push_clip_full (name, track, CLIP_TEMPLATE, prev, start);
+}
+
+static GstFlowReturn
+push_empty_clip (const gchar *name, const gchar *track, GstClockTime start)
+{
+  return push_clip_full (name, track,
+    EMPTY_CLIP_TEMPLATE, GST_CLOCK_TIME_NONE, start);
+}
+
 
 static void
-check_clip (const gchar * name, const gchar * track,
+check_output_clip (const gchar * name, const gchar * track,
     const gchar * start, const gchar * end)
 {
   gchar *decoded_clip;
@@ -236,58 +317,59 @@
   if (track == NULL)
     track = "default";
 
-  current_buf = current_buf->next;
-  fail_unless (g_list_length (current_buf));
   decoded_clip = g_strdup_printf (CLIP_TEMPLATE_DECODED, name, track, start);
   check_output_buffer_is_equal (name, decoded_clip, 1);
   g_free (decoded_clip);
 }
 
-static void
-check_end ()
-{
-  current_buf = current_buf->next;
-  check_output_buffer_is_equal ("cmml-end-tag", END_TAG, 1);
-}
-
 GST_START_TEST (test_dec)
 {
-  GstElement *cmmldec;
-
-  cmmldec = setup_cmmldec ();
+  GstClockTime clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND;
+  GstClockTime clip2_start = clip1_start;
+  GstClockTime clip3_start = 
+      ((100 * 3600) + (59 * 60) + 59) * GST_SECOND + 678 * GST_MSECOND;
 
   check_headers ();
 
-  push_clip ("clip-1", "default",
-      0, 1 * GST_SECOND + 234 * GST_MSECOND, 0, GST_FLOW_OK);
-  push_clip ("clip-2", "othertrack",
-      0, 4 * GST_SECOND + 321 * GST_MSECOND, 0, GST_FLOW_OK);
-  push_clip ("clip-3", "default",
-      1 * GST_SECOND + 234 * GST_MSECOND,
-      ((100 * 3600) + (59 * 60) + 59) * GST_SECOND + 678 * GST_MSECOND, 0,
-      GST_FLOW_OK);
+  flow = push_clip ("clip-1", "default", GST_CLOCK_TIME_NONE, clip1_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
+  flow = push_clip ("clip-2", "othertrack", GST_CLOCK_TIME_NONE, clip2_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
+  flow = push_clip ("clip-3", "default", clip1_start, clip3_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
   /* send EOS to flush clip-2 and clip-3 */
   gst_pad_send_event (GST_PAD_PEER (srcpad), gst_event_new_eos ());
 
-  check_clip ("clip-1", "default", "0:00:01.234", NULL);
-  check_clip ("clip-2", "othertrack", "0:00:04.321", NULL);
-  check_clip ("clip-3", "default", "100:59:59.678", NULL);
-  check_end ();
-
-  cleanup_cmmldec (cmmldec);
+  check_output_clip ("clip-1", "default", "0:00:01.234", NULL);
+  check_output_clip ("clip-2", "othertrack", "0:00:01.234", NULL);
+  check_output_clip ("clip-3", "default", "100:59:59.678", NULL);
+  check_output_buffer_is_equal ("cmml-end-tag", END_TAG, 1);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_preamble_no_pi)
+{
+  flow = push_data ("ident-header", IDENT_HEADER, IDENT_HEADER_SIZE, 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  fail_unless_equals_int (g_list_length (buffers), 0);
+
+  flow = push_data ("preamble-no-pi", 
+      PREAMBLE_NO_PI, strlen (PREAMBLE_NO_PI), 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  fail_unless_equals_int (g_list_length (buffers), 1);
+
+  check_output_buffer_is_equal ("cmml-preamble-buffer",
+      PREAMBLE_NO_PI "<cmml>", 1);
 }
 
 GST_END_TEST;
 
 GST_START_TEST (test_tags)
 {
-  GstElement *cmmldec;
-  GstBus *bus;
-  GstMessage *message;
-  GstTagList *tags;
-  const GValue *tag_val;
   GObject *tag;
-  gchar *title, *base;
   gboolean empty;
   gchar *id, *track;
   gint64 start_time, end_time;
@@ -295,51 +377,15 @@
   gchar *img_src, *img_alt;
   gchar *desc;
   GValueArray *meta;
-
-  cmmldec = setup_cmmldec ();
-  bus = gst_element_get_bus (cmmldec);
+  GstClockTime clip1_start;
 
   check_headers ();
 
-  /* read the GstCmmlTagHead tag */
-  message = gst_bus_poll (bus, GST_MESSAGE_TAG, -1);
-  fail_unless (message != NULL);
-
-  gst_message_parse_tag (message, &tags);
-  fail_unless (tags != NULL);
-
-  tag_val = gst_tag_list_get_value_index (tags, GST_TAG_CMML_HEAD, 0);
-  fail_unless (tag_val != NULL);
-
-  tag = g_value_get_object (tag_val);
-  fail_unless (tag != NULL);
-
-  g_object_get (tag, "title", &title, "base-uri", &base, "meta", &meta, NULL);
-  fail_unless_equals_string ("The Research Hunter", title);
-  fail_unless (base == NULL);
-  fail_unless (meta != NULL);
-  fail_unless_equals_int (meta->n_values, 10);
-
-  gst_message_unref (message);
-  gst_tag_list_free (tags);
-  g_free (title);
-  g_free (base);
-  g_value_array_free (meta);
-
-  push_clip ("clip-1", "default",
-      0, 1 * GST_SECOND + 234 * GST_MSECOND, 0, GST_FLOW_OK);
-
-  /* read the GstCmmlTagClip */
-  message = gst_bus_poll (bus, GST_MESSAGE_TAG, -1);
-  fail_unless (message != NULL);
-
-  gst_message_parse_tag (message, &tags);
-  fail_unless (tags != NULL);
-
-  tag_val = gst_tag_list_get_value_index (tags, GST_TAG_CMML_CLIP, 0);
-  fail_unless (tag_val != NULL);
-
-  tag = g_value_get_object (tag_val);
+  clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND;
+  flow = push_clip ("clip-1", "default", 0, clip1_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
+  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
   fail_unless (tag != NULL);
 
   g_object_get (tag, "id", &id, "empty", &empty, "track", &track,
@@ -369,40 +415,155 @@
   g_free (img_alt);
   g_free (desc);
   g_value_array_free (meta);
-  gst_tag_list_free (tags);
-  gst_message_unref (message);
-  gst_object_unref (bus);
-  cleanup_cmmldec (cmmldec);
-}
-
+  g_object_unref (tag);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_wait_clip_end)
+{
+  GObject *tag;
+  gchar *id;
+  GstClockTime end_time = 0;
+
+  /* usually every time cmmldec decodes a clip, it creates a GstCmmlTagClip and
+   * posts GST_MESSAGE_TAG on the bus. When wait-clip-end is TRUE, cmmldec waits
+   * before posting the message until another clip in the same track is decoded.
+   * This way it can set the previous clip's end-time property before posting
+   * the message */
+  g_object_set (cmmldec, "wait-clip-end-time", TRUE, NULL);
+
+  check_headers ();
+
+  GstClockTime clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND;
+  GstClockTime clip2_start = 2 * GST_SECOND + 234 * GST_MSECOND;
+  GstClockTime clip3_start = 3 * GST_SECOND + 234 * GST_MSECOND;
+  GstClockTime clip3_end = 4 * GST_SECOND + 234 * GST_MSECOND;
+  GstClockTime clip4_start = 5 * GST_SECOND + 234 * GST_MSECOND;
+
+  flow = push_clip ("clip-1", "default", 0, clip1_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  /* no tag has been posted yet */
+  fail_unless (cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP) == NULL);
+
+  flow = push_clip ("clip-2", "default", clip1_start, clip2_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
+  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
+  fail_unless (tag != NULL);
+  g_object_get (tag, "id", &id, "end-time", &end_time, NULL);
+  /* clip-1 is posted when clip-2 is decoded. Its end_time is set to clip-2's
+   * start_time */
+  fail_unless_equals_string (id, "clip-1");
+  fail_unless_equals_int (end_time, clip2_start);
+  g_free (id);
+  g_object_unref (tag);
+
+  flow = push_clip ("clip-3", "default", clip2_start, clip3_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+ 
+  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
+  fail_unless (tag != NULL);
+  g_object_get (tag, "id", &id, "end-time", &end_time, NULL);
+  /* clip-2 is posted when clip-3 is decoded. It ends when clip-3 starts */
+  fail_unless_equals_string (id, "clip-2");
+  fail_unless_equals_int (end_time, clip3_start);
+  g_free (id);
+  g_object_unref (tag);
+
+  flow = push_empty_clip ("empty-clip", "default", clip3_end);
+  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
+  fail_unless (tag != NULL);
+  g_object_get (tag, "id", &id, "end-time", &end_time, NULL);
+  /* clip-3 ends when empty-clip is decoded */
+  fail_unless_equals_string (id, "clip-3");
+  fail_unless_equals_int (end_time, clip3_end);
+  g_free (id);
+  g_object_unref (tag);
+
+  flow = push_clip ("clip-4", "default", clip3_start, clip4_start);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+ 
+  /* an empty clip just marks the end of the previous one, so no tag is posted
+   * for empty-clip */
+  fail_unless (cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP) == NULL);
+  /* send EOS to flush clip-4 */
+  gst_pad_send_event (GST_PAD_PEER (srcpad), gst_event_new_eos ());
+
+  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
+  fail_unless (tag != NULL);
+  g_object_get (tag, "id", &id, NULL);
+  fail_unless_equals_string (id, "clip-4");
+  g_free (id);
+  g_object_unref (tag);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_weird_input)
+{
+  const gchar *bad_xml = "<?xml version=\"1.0\"?><a><b></a>";
+  
+  /* plain wrong input */
+  flow = push_data ("bad-input", "garabage", strlen ("garbage"), 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  /* malformed ident header */
+  flow = push_data ("bad-ident-header", "CMML\0\0\0\0garbage", 15, 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  /* push invalid xml */
+  flow = push_data ("bad-xml", bad_xml, strlen (bad_xml), 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  /* and now for something completely different: an empty buffer. This is valid
+   * as the EOS page could contain no data */
+  flow = push_data ("empty-eos", NULL, 0, 0);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_convert)
+{
+  guint64 keyindex, keyoffset, granulepos;
+  GstClockTime index_time, offset_time;
+  GstFormat dstfmt = GST_FORMAT_TIME;
+  gint64 dstval;
+
+  /* send headers to set the granulerate */
+  check_headers ();
+
+  /* create a 1|1 granulepos */
+  index_time = 1 * GST_SECOND;
+  offset_time = 1 * GST_SECOND;
+
+  keyindex = (index_time / granulerate) << granuleshift;
+  keyoffset = offset_time / granulerate;
+  granulepos = keyindex + keyoffset;
+
+  fail_unless (gst_pad_query_convert (GST_PAD_PEER (srcpad),
+      GST_FORMAT_DEFAULT, granulepos, &dstfmt, &dstval));
+
+  fail_unless (dstfmt == GST_FORMAT_TIME);
+  /* fail unless dstval == index + offset */
+  fail_unless_equals_int (2 * GST_SECOND, dstval);
+}
 GST_END_TEST;
 
 Suite *
 cmmldec_suite ()
 {
   Suite *s = suite_create ("cmmldec");
-  TCase *tc_chain = tcase_create ("general");
+  TCase *tc_general = tcase_create ("general");
 
-  suite_add_tcase (s, tc_chain);
-  tcase_add_test (tc_chain, test_dec);
-  tcase_add_test (tc_chain, test_tags);
+  suite_add_tcase (s, tc_general);
+  tcase_add_checked_fixture (tc_general, setup_cmmldec, teardown_cmmldec);
+  tcase_add_test (tc_general, test_dec);
+  tcase_add_test (tc_general, test_tags);
+  tcase_add_test (tc_general, test_preamble_no_pi);
+  tcase_add_test (tc_general, test_wait_clip_end);
+  tcase_add_test (tc_general, test_convert);
+  tcase_add_test (tc_general, test_weird_input);
 
   return s;
 }
 
-int
-main (int argc, char **argv)
-{
-  int nf;
-
-  Suite *s = cmmldec_suite ();
-  SRunner *sr = srunner_create (s);
-
-  gst_check_init (&argc, &argv);
-
-  srunner_run_all (sr, CK_NORMAL);
-  nf = srunner_ntests_failed (sr);
-  srunner_free (sr);
-
-  return nf;
-}
+GST_CHECK_MAIN (cmmldec);

=== modified file 'tests/check/elements/cmmlenc.c'
--- tests/check/elements/cmmlenc.c	2006-07-29 14:32:26 +0000
+++ tests/check/elements/cmmlenc.c	2006-08-24 21:27:43 +0000
@@ -71,9 +71,6 @@
 
 #define HEAD_TAG_ENCODED HEAD_TAG
 
-#define END_TAG \
-  "</cmml>"
-
 #define CLIP_TEMPLATE \
   "<clip id=\"%s\" track=\"%s\" start=\"%s\">"\
     "<a href=\"http://www.annodex.org/\">http://www.annodex.org</a>"\
@@ -82,6 +79,14 @@
     "<meta name=\"test\" content=\"test content\"/>"\
   "</clip>"
 
+#define ENDED_CLIP_TEMPLATE \
+  "<clip id=\"%s\" track=\"%s\" start=\"%s\" end=\"%s\">"\
+    "<a href=\"http://www.annodex.org/\">http://www.annodex.org</a>"\
+    "<img src=\"images/index.jpg\"/>"\
+    "<desc>Annodex Foundation</desc>"\
+    "<meta name=\"test\" content=\"test content\"/>"\
+  "</clip>"
+
 #define CLIP_TEMPLATE_ENCODED \
   "<clip id=\"%s\" track=\"%s\">"\
     "<a href=\"http://www.annodex.org/\">http://www.annodex.org</a>"\
@@ -90,12 +95,28 @@
     "<meta name=\"test\" content=\"test content\"/>"\
   "</clip>"
 
+#define EMPTY_CLIP_TEMPLATE_ENCODED \
+  "<clip track=\"%s\"/>"
+
+#define fail_unless_equals_flow_return(a, b)                            \
+G_STMT_START {                                                          \
+  gchar *a_up = g_ascii_strup (gst_flow_get_name (a), -1);              \
+  gchar *b_up = g_ascii_strup (gst_flow_get_name (b), -1);              \
+  fail_unless (a == b,                                                  \
+      "'" #a "' (GST_FLOW_%s) is not equal to '" #b "' (GST_FLOW_%s)",  \
+      a_up, b_up);                                                      \
+  g_free (a_up);                                                        \
+  g_free (b_up);                                                        \
+} G_STMT_END;
+
 GList *buffers;
-GList *current_buf = NULL;
-guint64 granulerate;
-guint8 granuleshift;
-
-GstPad *srcpad, *sinkpad;
+static GList *current_buf;
+static guint64 granulerate;
+static guint8 granuleshift;
+static GstElement *cmmlenc;
+static GstBus *bus;
+static GstFlowReturn flow;
+static GstPad *srcpad, *sinkpad;
 
 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -109,7 +130,7 @@
     GST_STATIC_CAPS (SRC_CAPS)
     );
 
-GstBuffer *
+static GstBuffer *
 buffer_new (const gchar * buffer_data, guint size)
 {
   GstBuffer *buffer;
@@ -130,11 +151,9 @@
   gst_buffer_unref (GST_BUFFER (buffer));
 }
 
-GstElement *
+static void
 setup_cmmlenc ()
 {
-  GstElement *cmmlenc;
-  GstBus *bus;
   guint64 granulerate_n, granulerate_d;
 
   GST_DEBUG ("setup_cmmlenc");
@@ -155,25 +174,21 @@
       "granule-shift", &granuleshift, NULL);
 
   granulerate = GST_SECOND * granulerate_d / granulerate_n;
-  buffers = NULL;
-  return cmmlenc;
 }
 
 static void
-cleanup_cmmlenc (GstElement * cmmlenc)
+teardown_cmmlenc ()
 {
-  GstBus *bus;
-
   /* free encoded buffers */
   g_list_foreach (buffers, buffer_unref, NULL);
   g_list_free (buffers);
   buffers = NULL;
+  current_buf = NULL;
 
-  bus = GST_ELEMENT_BUS (cmmlenc);
   gst_bus_set_flushing (bus, TRUE);
   gst_object_unref (bus);
 
-  GST_DEBUG ("cleanup_cmmlenc");
+  GST_DEBUG ("teardown_cmmlenc");
   gst_check_teardown_src_pad (cmmlenc);
   gst_check_teardown_sink_pad (cmmlenc);
   gst_check_teardown_element (cmmlenc);
@@ -183,7 +198,15 @@
 check_output_buffer_is_equal (const gchar * name,
     const gchar * data, gint refcount)
 {
-  GstBuffer *buffer = GST_BUFFER (current_buf->data);
+  GstBuffer *buffer;
+
+  if (current_buf == NULL)
+    current_buf = buffers;
+  else
+    current_buf = g_list_next (current_buf);
+
+  fail_unless (current_buf != NULL);
+  buffer = GST_BUFFER (current_buf->data);
 
   ASSERT_OBJECT_REFCOUNT (buffer, name, refcount);
   fail_unless (memcmp (GST_BUFFER_DATA (buffer), data,
@@ -191,186 +214,273 @@
       "'%s' (%s) is not equal to (%s)", name, GST_BUFFER_DATA (buffer), data);
 }
 
-static void
+static GstFlowReturn
 push_data (const gchar * name,
-    const gchar * data, gint size, GstFlowReturn expected_return)
+    const gchar * data, gint size)
 {
   GstBuffer *buffer;
   GstFlowReturn res;
 
   buffer = buffer_new (data, size);
   res = gst_pad_push (srcpad, buffer);
-  fail_unless (res == expected_return,
-      "pushing %s returned %d not %d", name, res, expected_return);
+  
+  return res;
 }
 
 static void
 check_headers ()
 {
   /* push the cmml start tag */
-  push_data ("preamble", PREAMBLE, strlen (PREAMBLE), GST_FLOW_OK);
+  flow = push_data ("preamble", PREAMBLE, strlen (PREAMBLE));
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+
   /* push the stream tag */
-  push_data ("stream", STREAM_TAG, strlen (STREAM_TAG), GST_FLOW_OK);
+  flow = push_data ("stream", STREAM_TAG, strlen (STREAM_TAG));
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
   /* push the head tag */
-  push_data ("head", HEAD_TAG, strlen (HEAD_TAG), GST_FLOW_OK);
+  flow = push_data ("head", HEAD_TAG, strlen (HEAD_TAG));
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
 
-  /* should output the cmml ident header and the cmml start tag transformed
-   * into a processing instruction */
-  current_buf = buffers;
-  fail_unless_equals_int (g_list_length (current_buf), 3);
+  /* should output 3 buffers: the ident, preamble and head headers */
+  fail_unless_equals_int (g_list_length (buffers), 3);
 
   /* check the ident header */
   check_output_buffer_is_equal ("cmml-ident-buffer", IDENT_HEADER, 1);
 
   /* check the cmml processing instruction */
-  current_buf = current_buf->next;
   check_output_buffer_is_equal ("cmml-preamble-buffer", PREAMBLE_ENCODED, 1);
 
   /* check the encoded head tag */
-  current_buf = current_buf->next;
   check_output_buffer_is_equal ("head-tag-buffer", HEAD_TAG_ENCODED, 1);
 }
 
-static void
+static GstFlowReturn
 push_clip (const gchar * name, const gchar * track,
-    const gchar * start, const gchar * end, GstFlowReturn expected_return)
+    const gchar * start, const gchar * end)
 {
   gchar *clip;
-
-  if (track == NULL)
-    track = "default";
-
-  clip = g_strdup_printf (CLIP_TEMPLATE, name, track, start);
-  push_data (name, clip, strlen (clip), expected_return);
+  GstFlowReturn res;
+
+  if (end != NULL)
+    clip = g_strdup_printf (ENDED_CLIP_TEMPLATE, name, track, start, end);
+  else
+    clip = g_strdup_printf (CLIP_TEMPLATE, name, track, start);
+  res = push_data (name, clip, strlen (clip));
   g_free (clip);
+
+  return res;
 }
 
 static void
-check_clip (const gchar * name, const gchar * track,
-    GstClockTime start, GstClockTime prev)
-{
-  gchar *encoded_clip;
-  GstBuffer *buffer;
-  gint64 keyindex, keyoffset, granulepos;
-
-  if (track == NULL)
-    track = "default";
-
-  current_buf = current_buf->next;
-  fail_unless (g_list_length (current_buf));
-  encoded_clip = g_strdup_printf (CLIP_TEMPLATE_ENCODED, name, track);
-  check_output_buffer_is_equal (name, encoded_clip, 1);
-  g_free (encoded_clip);
-  buffer = GST_BUFFER (current_buf->data);
-  granulepos = GST_BUFFER_OFFSET_END (GST_BUFFER (buffer));
-  keyindex = granulepos >> granuleshift;
+check_clip_times (GstBuffer *buffer, GstClockTime start, GstClockTime prev)
+{  
+  guint64 keyindex, keyoffset, granulepos;
+
+  granulepos = GST_BUFFER_OFFSET_END (buffer);
+  if (granuleshift == 0 || granuleshift == 64)
+    keyindex = 0;
+  else
+    keyindex = granulepos >> granuleshift;
   keyoffset = granulepos - (keyindex << granuleshift);
   fail_unless_equals_uint64 (keyindex * granulerate, prev);
   fail_unless_equals_uint64 ((keyindex + keyoffset) * granulerate, start);
 }
 
 static void
-push_end ()
+check_clip (const gchar * name, const gchar * track,
+    GstClockTime start, GstClockTime prev)
 {
-  push_data ("end", END_TAG, strlen (END_TAG), GST_FLOW_OK);
+  gchar *encoded_clip;
+  GstBuffer *buffer;
+
+  encoded_clip = g_strdup_printf (CLIP_TEMPLATE_ENCODED, name, track);
+  check_output_buffer_is_equal (name, encoded_clip, 1);
+  g_free (encoded_clip);
+  buffer = GST_BUFFER (current_buf->data);
+  check_clip_times (buffer, start, prev);
 }
 
 static void
-check_end ()
+check_empty_clip (const gchar *name, const gchar *track,
+    GstClockTime start, GstClockTime prev)
 {
-  /* should output the EOS page */
-  current_buf = current_buf->next;
-  fail_unless_equals_int (g_list_length (current_buf), 1);
-  check_output_buffer_is_equal ("cmml-eos-buffer", NULL, 1);
+  gchar *encoded_clip;
+  GstBuffer *buffer;
+
+  encoded_clip = g_strdup_printf (EMPTY_CLIP_TEMPLATE_ENCODED, track);
+  check_output_buffer_is_equal (name, encoded_clip, 1);
+  g_free (encoded_clip);
+  buffer = GST_BUFFER (current_buf->data);
+  check_clip_times (buffer, start, prev);
 }
 
 GST_START_TEST (test_enc)
 {
-  GstElement *cmmlenc;
-
-  cmmlenc = setup_cmmlenc ();
-
-  check_headers ();
-
-  push_clip ("clip-1", "default", "1.234", NULL, GST_FLOW_OK);
-  check_clip ("clip-1", "default", 1234 * granulerate, 0);
-
-  push_clip ("clip-2", NULL, "5.678", NULL, GST_FLOW_OK);
-  check_clip ("clip-2", "default", 5678 * granulerate, 1234 * granulerate);
-
-  push_clip ("clip-3", "othertrack", "9.123", NULL, GST_FLOW_OK);
-  check_clip ("clip-3", "othertrack", 9123 * granulerate, 0);
-
-  push_end ();
-  check_end ();
-
-  cleanup_cmmlenc (cmmlenc);
-}
-
-GST_END_TEST;
-
-GST_START_TEST (test_bad_start_time)
-{
-  GstElement *cmmlenc;
-
-  cmmlenc = setup_cmmlenc ();
-
-  check_headers ();
-
-  push_clip ("clip-1", "default", "1000:00:00.000", NULL, GST_FLOW_OK);
-  check_clip ("clip-1", "default", (guint64) 3600000 * 1000 * granulerate, 0);
-
-  /* keyindex overflow: npt:1000:00:00.000 doesn't fit in 32 bits */
-  push_clip ("clip-2", NULL, "5.678", NULL, GST_FLOW_ERROR);
-
-  /* other tracks should work */
-  push_clip ("clip-3", "othertrack", "9.123", NULL, GST_FLOW_OK);
-  check_clip ("clip-3", "othertrack", 9123 * granulerate, 0);
-
-  /* bad msecs */
-  push_clip ("clip-bad-msecs", "default", "0.1000", NULL, GST_FLOW_ERROR);
-
-  /* bad secs */
-  push_clip ("clip-bad-secs", "default", "00:00:60.123", NULL, GST_FLOW_ERROR);
-
-  /* bad minutes */
-  push_clip ("clip-bad-minutes", "default", "00:60:12.345",
-      NULL, GST_FLOW_ERROR);
-
-  /* bad hours */
-  push_clip ("clip-bad-hours", "default", "10000:12:34.567",
-      NULL, GST_FLOW_ERROR);
-
-  push_end ();
-  check_end ();
-
-  cleanup_cmmlenc (cmmlenc);
-}
-GST_END_TEST static Suite *
+  check_headers ();
+
+  flow = push_clip ("clip-1", "default", "1.234", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-1", "default", 1 * GST_SECOND + 234 * GST_MSECOND, 0);
+
+  flow = push_clip ("clip-2", "default", "5.678", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-2", "default",
+      5 * GST_SECOND + 678 * GST_MSECOND,
+      1 * GST_SECOND + 234 * GST_MSECOND);
+
+  flow = push_clip ("clip-3", "othertrack", "9.123", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-3", "othertrack", 9 * GST_SECOND + 123 * GST_MSECOND, 0);
+  
+  flow = push_data ("end-tag", "</cmml>", strlen ("</cmml>"));
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_output_buffer_is_equal ("cmml-eos", NULL, 1);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_clip_end_time)
+{
+  check_headers ();
+
+  /* push a clip that starts at 1.234 an ends at 2.234 */
+  flow = push_clip ("clip-1", "default", "1.234", "2.234");
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-1", "default", 1 * GST_SECOND + 234 * GST_MSECOND, 0);
+
+  /* now check that the encoder created an empty clip starting at 2.234 to mark
+   * the end of clip-1 */
+  check_empty_clip ("clip-1-end", "default",
+      2 * GST_SECOND + 234 * GST_MSECOND, 1 * GST_SECOND + 234 * GST_MSECOND);
+
+  /* now push another clip on the same track and check that the keyindex part of
+   * the granulepos points to clip-1 and not to the empty clip */
+  flow = push_clip ("clip-2", "default", "5", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-2", "default",
+      5 * GST_SECOND, 1 * GST_SECOND + 234 * GST_MSECOND);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_time_order)
+{
+  check_headers ();
+
+  /* clips belonging to the same track must have start times in non decreasing
+   * order */
+  flow = push_clip ("clip-1", "default", "1000:00:00.000", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-1", "default", 3600 * 1000 * GST_SECOND, 0);
+
+  flow = push_clip ("clip-2", "default", "5.678", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  flow = push_clip ("clip-3", "default", "1000:00:00.001", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-3", "default",
+      3600 * 1000 * GST_SECOND + 1 * GST_MSECOND,
+      3600 * 1000 * GST_SECOND);
+
+  /* tracks don't interfere with each other */
+  flow = push_clip ("clip-4", "othertrack", "9.123", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("clip-4", "othertrack", 9 * GST_SECOND +123 * GST_MSECOND, 0);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_time_parsing)
+{
+  check_headers ();
+
+  flow = push_clip ("bad-msecs", "default", "0.1000", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  flow = push_clip ("bad-secs", "default", "00:00:60.123", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  flow = push_clip ("bad-minutes", "default", "00:60:12.345", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  /* this fails since we can't store 5124096 * 3600 * GST_SECOND in a
+   * GstClockTime */
+  flow = push_clip ("bad-hours", "default", "5124096:00:00.000", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+}
+GST_END_TEST;
+
+GST_START_TEST (test_time_limits)
+{
+  check_headers ();
+
+  /* ugly hack to make sure that the following checks actually overflow parsing
+   * the times in gst_cmml_clock_time_from_npt rather than converting them to
+   * granulepos in gst_cmml_clock_time_to_granule */
+  granuleshift = 64;
+  g_object_set (cmmlenc, "granule-shift", granuleshift, NULL);
+
+  /* 5124095:34:33.709 is the max npt-hhmmss time representable with
+   * GstClockTime */
+  flow = push_clip ("max-npt-hhmmss", "foo", "5124095:34:33.709", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("max-npt-hhmmss", "foo",
+      (GstClockTime) 5124095 * 3600 * GST_SECOND + 34 * 60 * GST_SECOND +
+        33 * GST_SECOND + 709 * GST_MSECOND, 0);
+
+  flow = push_clip ("overflow-max-npt-hhmmss", "overflows",
+      "5124095:34:33.710", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  /* 18446744073.709 is the max ntp-sec time */
+  flow = push_clip ("max-npt-secs", "bar", "18446744073.709", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("max-npt-secs", "bar",
+      (GstClockTime) 5124095 * 3600 * GST_SECOND + 34 * 60 * GST_SECOND +
+        33 * GST_SECOND + 709 * GST_MSECOND, 0);
+
+  /* overflow doing 18446744074 * GST_SECOND */
+  flow = push_clip ("overflow-max-npt-secs", "overflows",
+      "18446744074.000", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+
+  /* overflow doing seconds + milliseconds */
+  flow = push_clip ("overflow-max-npt-secs-msecs", "overflows",
+      "18446744073.710", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);
+ 
+  /* reset granuleshift to 32 to check keyoffset overflows in
+   * gst_cmml_clock_time_to_granule */
+  granuleshift = 32;
+  g_object_set (cmmlenc, "granule-shift", granuleshift, NULL);
+
+  /* 1193:02:47.295 is the max time we can encode in the keyoffset part of a
+   * granulepos given a granuleshift of 32 */
+  flow = push_clip ("max-granule-keyoffset", "baz",
+      "1193:02:47.295", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
+  check_clip ("max-granule-keyoffset", "baz",
+      1193 * 3600 * GST_SECOND + 2 * 60 * GST_SECOND +
+        47 * GST_SECOND + 295 * GST_MSECOND, 0);
+
+  flow = push_clip ("overflow-max-granule-keyoffset", "overflows",
+      "1193:02:47.296", NULL);
+  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); 
+}
+GST_END_TEST;
+
+static Suite *
 cmmlenc_suite ()
 {
   Suite *s = suite_create ("cmmlenc");
-  TCase *tc_chain = tcase_create ("general");
-
-  suite_add_tcase (s, tc_chain);
-  tcase_add_test (tc_chain, test_enc);
-  tcase_add_test (tc_chain, test_bad_start_time);
+  TCase *tc_general = tcase_create ("general");
+
+  suite_add_tcase (s, tc_general);
+  tcase_add_checked_fixture (tc_general, setup_cmmlenc, teardown_cmmlenc);
+  tcase_add_test (tc_general, test_enc);
+  tcase_add_test (tc_general, test_clip_end_time);
+  tcase_add_test (tc_general, test_time_order);
+  tcase_add_test (tc_general, test_time_parsing);
+  tcase_add_test (tc_general, test_time_limits);
+
   return s;
 }
 
-int
-main (int argc, char **argv)
-{
-  int nf;
-
-  Suite *s = cmmlenc_suite ();
-  SRunner *sr = srunner_create (s);
-
-  gst_check_init (&argc, &argv);
-
-  srunner_run_all (sr, CK_NORMAL);
-  nf = srunner_ntests_failed (sr);
-  srunner_free (sr);
-
-  return nf;
-}
+GST_CHECK_MAIN (cmmlenc);

