hlsdemux: tests: add unit tests for hlsdemux
authorAlex Ashley <bugzilla@ashley-family.net>
Tue, 10 Nov 2015 16:41:02 +0000 (16:41 +0000)
committerThiago Santos <thiagoss@osg.samsung.com>
Tue, 22 Dec 2015 14:15:32 +0000 (11:15 -0300)
Using the new GstAdaptiveDemux test framework, add tests that
exercise hlsdemux. The following tests are added:

simpleTest
A simple playlist that contains some media URLs

testMediaPlaylist
A master playlist with a variant playlist that contains media URLs

testMediaPlaylistNotFound
A master playlist that points to a missing variant playlist

testFragmentNotFound
A master playlist with a variant playlist that contains media URLs
There is a missing media file referenced from the variant playlist.

testFragmentDownloadError
A master playlist with a variant playlist that contains media URLs
During the download of one media file, the test simulates the network
connection being dropped.

testSeek
A simple test of trying to perform a seek on an HLS stream.

tests/check/Makefile.am
tests/check/elements/.gitignore
tests/check/elements/hls_demux.c [new file with mode: 0644]

index e558782..17ed637 100644 (file)
@@ -169,8 +169,10 @@ check_curl_sftp =
 endif
 
 if USE_HLS
-check_hlsdemux = elements/hlsdemux_m3u8
+check_hlsdemux_m3u8 = elements/hlsdemux_m3u8
+check_hls_demux = elements/hls_demux
 else
+check_hlsdemux_m3u8 =
 check_hlsdemux =
 endif
 
@@ -288,7 +290,8 @@ check_PROGRAMS = \
        $(check_orc) \
        libs/insertbin \
        $(check_gl) \
-       $(check_hlsdemux) \
+       $(check_hlsdemux_m3u8) \
+       $(check_hls_demux) \
        $(EXPERIMENTAL_CHECKS)
 
 noinst_HEADERS = elements/mxfdemux.h
@@ -570,6 +573,13 @@ elements_hlsdemux_m3u8_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) -I$(top_srcdir)/
 elements_hlsdemux_m3u8_LDADD = $(GST_BASE_LIBS) $(LDADD)
 elements_hlsdemux_m3u8_SOURCES = elements/hlsdemux_m3u8.c
 
+elements_hls_demux_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_PLUGINS_BAD_CFLAGS)
+elements_hls_demux_LDADD = $(GST_BASE_LIBS) $(LDADD) \
+       -lgsttag-$(GST_API_VERSION) \
+       -lgstapp-$(GST_API_VERSION) \
+       $(top_builddir)/gst-libs/gst/adaptivedemux/libgstadaptivedemux-@GST_API_VERSION@.la
+elements_hls_demux_SOURCES = elements/test_http_src.c elements/test_http_src.h elements/adaptive_demux_engine.c elements/adaptive_demux_engine.h elements/adaptive_demux_common.c elements/adaptive_demux_common.h elements/hls_demux.c
+
 orc_compositor_CFLAGS = $(ORC_CFLAGS)
 orc_compositor_LDADD = $(ORC_LIBS) -lorc-test-0.4
 nodist_orc_compositor_SOURCES = orc/compositor.c
index 1a52a03..241c441 100644 (file)
@@ -26,6 +26,7 @@ glimagesink
 h263parse
 h264parse
 hlsdemux_m3u8
+hls_demux
 id3mux
 imagecapturebin
 jifmux
diff --git a/tests/check/elements/hls_demux.c b/tests/check/elements/hls_demux.c
new file mode 100644 (file)
index 0000000..0918766
--- /dev/null
@@ -0,0 +1,594 @@
+/* GStreamer unit test for HLS demux
+ *
+ * Copyright (c) <2015> YouView TV Ltd
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/check/gstcheck.h>
+#include "adaptive_demux_common.h"
+
+#define DEMUX_ELEMENT_NAME "hlsdemux"
+
+#define TS_PACKET_LEN 188
+
+typedef struct _GstHlsDemuxTestInputData
+{
+  const gchar *uri;
+  const guint8 *payload;
+  guint64 size;
+} GstHlsDemuxTestInputData;
+
+typedef struct _GstHlsDemuxTestCase
+{
+  const GstHlsDemuxTestInputData *input;
+  GstStructure *state;
+} GstHlsDemuxTestCase;
+
+typedef struct _GstHlsDemuxTestAppendUriContext
+{
+  GQuark field_id;
+  const gchar *uri;
+} GstHlsDemuxTestAppendUriContext;
+
+typedef struct _GstHlsDemuxTestSelectBitrateContext
+{
+  GstAdaptiveDemuxTestEngine *engine;
+  GstAdaptiveDemuxTestCase *testData;
+  guint select_count;
+  gulong signal_handle;
+} GstHlsDemuxTestSelectBitrateContext;
+
+static GByteArray *
+generate_transport_stream (guint length)
+{
+  guint pos;
+  guint cc = 0;
+  GByteArray *mpeg_ts;
+
+  fail_unless ((length % TS_PACKET_LEN) == 0);
+  mpeg_ts = g_byte_array_sized_new (length);
+  if (!mpeg_ts) {
+    return NULL;
+  }
+  memset (mpeg_ts->data, 0xFF, length);
+  for (pos = 0; pos < length; pos += TS_PACKET_LEN) {
+    mpeg_ts->data[pos] = 0x47;
+    mpeg_ts->data[pos + 1] = 0x1F;
+    mpeg_ts->data[pos + 2] = 0xFF;
+    mpeg_ts->data[pos + 3] = cc;
+    cc = (cc + 1) & 0x0F;
+  }
+  return mpeg_ts;
+}
+
+static GByteArray *
+setup_test_variables (GstHlsDemuxTestInputData * inputTestData,
+    GstAdaptiveDemuxTestExpectedOutput * outputTestData,
+    GstHlsDemuxTestCase * hlsTestCase,
+    GstAdaptiveDemuxTestCase * engineTestData, guint segment_size)
+{
+  GByteArray *mpeg_ts = NULL;
+
+  if (segment_size) {
+    mpeg_ts = generate_transport_stream ((segment_size));
+    fail_unless (mpeg_ts != NULL);
+    for (guint itd = 0; inputTestData[itd].uri; ++itd) {
+      if (g_str_has_suffix (inputTestData[itd].uri, ".ts")) {
+        inputTestData[itd].payload = mpeg_ts->data;
+      }
+    }
+    for (guint otd = 0; outputTestData[otd].name; ++otd) {
+      outputTestData[otd].expected_data = mpeg_ts->data;
+      engineTestData->output_streams =
+          g_list_append (engineTestData->output_streams, &outputTestData[otd]);
+    }
+  }
+  hlsTestCase->input = inputTestData;
+  hlsTestCase->state = gst_structure_new_empty (__FUNCTION__);
+  return mpeg_ts;
+}
+
+#define TESTCASE_INIT_BOILERPLATE(segment_size) \
+  GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; \
+  GstAdaptiveDemuxTestCallbacks engine_callbacks = { 0 }; \
+  GstAdaptiveDemuxTestCase *engineTestData; \
+  GstHlsDemuxTestCase hlsTestCase = { 0 }; \
+  GByteArray *mpeg_ts=NULL; \
+  engineTestData = gst_adaptive_demux_test_case_new(); \
+  fail_unless (engineTestData!=NULL); \
+  mpeg_ts = setup_test_variables(inputTestData, outputTestData, \
+                                 &hlsTestCase, engineTestData, segment_size); \
+
+#define TESTCASE_UNREF_BOILERPLATE do{ \
+  if(engineTestData->signal_context){ \
+    g_slice_free (GstHlsDemuxTestSelectBitrateContext, engineTestData->signal_context);        \
+  } \
+  if(mpeg_ts) { g_byte_array_free (mpeg_ts, TRUE); }         \
+  gst_structure_free (hlsTestCase.state); \
+  g_object_unref (engineTestData); \
+} while(0)
+
+static gboolean
+append_request_uri (GQuark field_id, GValue * value, gpointer user_data)
+{
+  GstHlsDemuxTestAppendUriContext *context =
+      (GstHlsDemuxTestAppendUriContext *) user_data;
+  GValue uri_val = G_VALUE_INIT;
+
+  if (context->field_id == field_id) {
+    g_value_init (&uri_val, G_TYPE_STRING);
+    g_value_set_string (&uri_val, context->uri);
+    gst_value_array_append_value (value, &uri_val);
+    g_value_unset (&uri_val);
+  }
+  return TRUE;
+}
+
+static void
+gst_hlsdemux_test_set_input_data (const GstHlsDemuxTestCase * test_case,
+    const GstHlsDemuxTestInputData * input, GstTestHTTPSrcInput * output)
+{
+  output->size = input->size;
+  output->context = (gpointer) input;
+  if (output->size == 0) {
+    output->size = strlen ((gchar *) input->payload);
+  }
+  fail_unless (input->uri != NULL);
+  if (g_str_has_suffix (input->uri, ".m3u8")) {
+    output->response_headers = gst_structure_new ("response-headers",
+        "Content-Type", G_TYPE_STRING, "application/vnd.apple.mpegurl", NULL);
+  } else if (g_str_has_suffix (input->uri, ".ts")) {
+    output->response_headers = gst_structure_new ("response-headers",
+        "Content-Type", G_TYPE_STRING, "video/mp2t", NULL);
+  }
+  if (gst_structure_has_field (test_case->state, "requests")) {
+    GstHlsDemuxTestAppendUriContext context =
+        { g_quark_from_string ("requests"), input->uri };
+    gst_structure_map_in_place (test_case->state, append_request_uri, &context);
+  } else {
+    GValue requests = G_VALUE_INIT;
+    GValue uri_val = G_VALUE_INIT;
+
+    g_value_init (&requests, GST_TYPE_ARRAY);
+    g_value_init (&uri_val, G_TYPE_STRING);
+    g_value_set_string (&uri_val, input->uri);
+    gst_value_array_append_value (&requests, &uri_val);
+    gst_structure_set_value (test_case->state, "requests", &requests);
+    g_value_unset (&uri_val);
+    g_value_unset (&requests);
+  }
+}
+
+static gboolean
+gst_hlsdemux_test_src_start (GstTestHTTPSrc * src,
+    const gchar * uri, GstTestHTTPSrcInput * input_data, gpointer user_data)
+{
+  const GstHlsDemuxTestCase *test_case =
+      (const GstHlsDemuxTestCase *) user_data;
+  guint fail_count = 0;
+
+  GST_DEBUG ("src_start %s", uri);
+  for (guint i = 0; test_case->input[i].uri; ++i) {
+    if (strcmp (test_case->input[i].uri, uri) == 0) {
+      gst_hlsdemux_test_set_input_data (test_case, &test_case->input[i],
+          input_data);
+      GST_DEBUG ("open URI %s", uri);
+      return TRUE;
+    }
+  }
+  gst_structure_get_uint (test_case->state, "failure-count", &fail_count);
+  fail_count++;
+  gst_structure_set (test_case->state, "failure-count", G_TYPE_UINT,
+      fail_count, NULL);
+  return FALSE;
+}
+
+static GstFlowReturn
+gst_hlsdemux_test_src_create (GstTestHTTPSrc * src,
+    guint64 offset,
+    guint length, GstBuffer ** retbuf, gpointer context, gpointer user_data)
+{
+  GstBuffer *buf;
+  /*  const GstHlsDemuxTestCase *test_case = (const GstHlsDemuxTestCase *) user_data; */
+  GstHlsDemuxTestInputData *input = (GstHlsDemuxTestInputData *) context;
+
+  buf = gst_buffer_new_allocate (NULL, length, NULL);
+  fail_if (buf == NULL, "Not enough memory to allocate buffer");
+  fail_if (input->payload == NULL);
+  gst_buffer_fill (buf, 0, input->payload + offset, length);
+  *retbuf = buf;
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_hlsdemux_test_network_error_src_create (GstTestHTTPSrc * src,
+    guint64 offset,
+    guint length, GstBuffer ** retbuf, gpointer context, gpointer user_data)
+{
+  const GstHlsDemuxTestCase *test_case =
+      (const GstHlsDemuxTestCase *) user_data;
+  GstHlsDemuxTestInputData *input = (GstHlsDemuxTestInputData *) context;
+  const gchar *failure_suffix;
+  guint64 failure_position = 0;
+
+  fail_unless (test_case != NULL);
+  fail_unless (input != NULL);
+  fail_unless (input->uri != NULL);
+  failure_suffix =
+      gst_structure_get_string (test_case->state, "failure-suffix");
+  if (!failure_suffix) {
+    failure_suffix = ".ts";
+  }
+  if (!gst_structure_get_uint64 (test_case->state, "failure-position",
+          &failure_position)) {
+    failure_position = 10 * TS_PACKET_LEN;
+  }
+  GST_DEBUG ("network_error %s %s %" G_GUINT64_FORMAT " @ %" G_GUINT64_FORMAT,
+      input->uri, failure_suffix, offset, failure_position);
+  if (g_str_has_suffix (input->uri, failure_suffix)
+      && offset >= failure_position) {
+    GST_DEBUG ("return error");
+    GST_ELEMENT_ERROR (src, RESOURCE, READ,
+        (("A network error occurred, or the server closed the connection unexpectedly.")), ("A network error occurred, or the server closed the connection unexpectedly."));
+    *retbuf = NULL;
+    return GST_FLOW_ERROR;
+  }
+  return gst_hlsdemux_test_src_create (src, offset, length, retbuf, context,
+      user_data);
+}
+
+/******************** Test specific code starts here **************************/
+
+/*
+ * Test a media manifest with a single segment
+ *
+ */
+GST_START_TEST (simpleTest)
+{
+  /* segment_size needs to larger than 2K, otherwise gsthlsdemux will
+     not perform a typefind on the buffer */
+  const guint segment_size = 30 * TS_PACKET_LEN;
+  const gchar *manifest =
+      "#EXTM3U \n"
+      "#EXT-X-TARGETDURATION:1\n"
+      "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
+  GstHlsDemuxTestInputData inputTestData[] = {
+    {"http://unit.test/media.m3u8", (guint8 *) manifest, 0},
+    {"http://unit.test/001.ts", NULL, segment_size},
+    {NULL, NULL, 0},
+  };
+  GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
+    {"src_0", segment_size, NULL},
+    {NULL, 0, NULL}
+  };
+  TESTCASE_INIT_BOILERPLATE (segment_size);
+
+  http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
+  http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
+  engine_callbacks.appsink_received_data =
+      gst_adaptive_demux_test_check_received_data;
+  engine_callbacks.appsink_eos =
+      gst_adaptive_demux_test_check_size_of_received_data;
+
+  gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
+  gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
+      inputTestData[0].uri, &engine_callbacks, engineTestData);
+  TESTCASE_UNREF_BOILERPLATE;
+}
+
+GST_END_TEST;
+
+GST_START_TEST (testMasterPlaylist)
+{
+  const guint segment_size = 30 * TS_PACKET_LEN;
+  const gchar *master_playlist =
+      "#EXTM3U\n"
+      "#EXT-X-VERSION:4\n"
+      "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
+      "1200.m3u8\n";
+  const gchar *media_playlist =
+      "#EXTM3U \n"
+      "#EXT-X-TARGETDURATION:1\n"
+      "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
+  GstHlsDemuxTestInputData inputTestData[] = {
+    {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
+    {"http://unit.test/1200.m3u8", (guint8 *) media_playlist, 0},
+    {"http://unit.test/001.ts", NULL, segment_size},
+    {NULL, NULL, 0}
+  };
+  GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
+    {"src_0", segment_size, NULL},
+    {NULL, 0, NULL}
+  };
+  const GValue *requests;
+  TESTCASE_INIT_BOILERPLATE (segment_size);
+
+  http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
+  http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
+  engine_callbacks.appsink_received_data =
+      gst_adaptive_demux_test_check_received_data;
+  engine_callbacks.appsink_eos =
+      gst_adaptive_demux_test_check_size_of_received_data;
+
+  gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
+  gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
+      "http://unit.test/master.m3u8", &engine_callbacks, engineTestData);
+
+  requests = gst_structure_get_value (hlsTestCase.state, "requests");
+  fail_unless (requests != NULL);
+  assert_equals_uint64 (gst_value_array_get_size (requests),
+      sizeof (inputTestData) / sizeof (inputTestData[0]) - 1);
+  for (guint i = 0; inputTestData[i].uri; ++i) {
+    const GValue *uri;
+    uri = gst_value_array_get_value (requests, i);
+    fail_unless (uri != NULL);
+    assert_equals_string (inputTestData[i].uri, g_value_get_string (uri));
+  }
+  TESTCASE_UNREF_BOILERPLATE;
+}
+
+GST_END_TEST;
+
+/*
+ * Test seeking
+ *
+ */
+GST_START_TEST (testSeek)
+{
+  const guint segment_size = 60 * TS_PACKET_LEN;
+  const gchar *manifest =
+      "#EXTM3U \n"
+      "#EXT-X-TARGETDURATION:1\n"
+      "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
+  GstHlsDemuxTestInputData inputTestData[] = {
+    {"http://unit.test/media.m3u8", (guint8 *) manifest, 0},
+    {"http://unit.test/001.ts", NULL, segment_size},
+    {NULL, NULL, 0},
+  };
+  GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
+    {"src_0", segment_size, NULL},
+    {NULL, 0, NULL}
+  };
+  GstTestHTTPSrcCallbacks http_src_callbacks = { 0 };
+  GstAdaptiveDemuxTestCase *engineTestData;
+  GstHlsDemuxTestCase hlsTestCase = { 0 };
+  GByteArray *mpeg_ts = NULL;
+
+  engineTestData = gst_adaptive_demux_test_case_new ();
+  mpeg_ts = setup_test_variables (inputTestData, outputTestData,
+      &hlsTestCase, engineTestData, segment_size);
+
+  http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
+  http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
+  engineTestData->threshold_for_seek = 20 * TS_PACKET_LEN;
+
+  gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
+  gst_adaptive_demux_test_seek (DEMUX_ELEMENT_NAME,
+      inputTestData[0].uri, engineTestData);
+
+  TESTCASE_UNREF_BOILERPLATE;
+}
+
+GST_END_TEST;
+
+static void
+testDownloadErrorMessageCallback (GstAdaptiveDemuxTestEngine * engine,
+    GstMessage * msg, gpointer user_data)
+{
+  GError *err = NULL;
+  gchar *dbg_info = NULL;
+
+  fail_unless (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR);
+  gst_message_parse_error (msg, &err, &dbg_info);
+  GST_DEBUG ("Error from element %s : %s\n",
+      GST_OBJECT_NAME (msg->src), err->message);
+  fail_unless_equals_string (GST_OBJECT_NAME (msg->src), DEMUX_ELEMENT_NAME);
+  g_error_free (err);
+  g_free (dbg_info);
+  g_main_loop_quit (engine->loop);
+}
+
+/* test failing to download the media playlist */
+GST_START_TEST (testMediaPlaylistNotFound)
+{
+  const gchar *master_playlist =
+      "#EXTM3U\n"
+      "#EXT-X-VERSION:4\n"
+      "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
+      "1200.m3u8\n";
+  GstHlsDemuxTestInputData inputTestData[] = {
+    {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
+    {NULL, NULL, 0}
+  };
+  GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
+    {"src_0", 0, NULL},
+    {NULL, 0, NULL}
+  };
+  TESTCASE_INIT_BOILERPLATE (0);
+
+  gst_structure_set (hlsTestCase.state,
+      "failure-count", G_TYPE_UINT, 0,
+      "failure-suffix", G_TYPE_STRING, "1200.m3u8", NULL);
+  http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
+  http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
+  engine_callbacks.appsink_received_data =
+      gst_adaptive_demux_test_check_received_data;
+  engine_callbacks.bus_error_message = testDownloadErrorMessageCallback;
+
+  gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
+  gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
+      "http://unit.test/master.m3u8", &engine_callbacks, engineTestData);
+
+  TESTCASE_UNREF_BOILERPLATE;
+}
+
+GST_END_TEST;
+
+static void
+hlsdemux_test_check_no_data_received (GstAdaptiveDemuxTestEngine
+    * engine, GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data)
+{
+  assert_equals_uint64 (stream->total_received_size, 0);
+  g_main_loop_quit (engine->loop);
+}
+
+/* test failing to download a media segment (a 404 error) */
+GST_START_TEST (testFragmentNotFound)
+{
+  const gchar *master_playlist =
+      "#EXTM3U\n"
+      "#EXT-X-VERSION:4\n"
+      "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
+      "1200.m3u8\n";
+  const gchar *media_playlist =
+      "#EXTM3U \n"
+      "#EXT-X-TARGETDURATION:1\n"
+      "#EXTINF:1,Test\n" "001.ts\n" "#EXT-X-ENDLIST\n";
+  GstHlsDemuxTestInputData inputTestData[] = {
+    {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
+    {"http://unit.test/1200.m3u8", (guint8 *) media_playlist, 0},
+    {NULL, NULL, 0}
+  };
+  GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
+    {"src_0", 0, NULL},
+    {NULL, 0, NULL}
+  };
+  TESTCASE_INIT_BOILERPLATE (0);
+
+  gst_structure_set (hlsTestCase.state,
+      "failure-count", G_TYPE_UINT, 0,
+      "failure-suffix", G_TYPE_STRING, "001.ts", NULL);
+  http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
+  http_src_callbacks.src_create = gst_hlsdemux_test_src_create;
+  engine_callbacks.appsink_received_data =
+      gst_adaptive_demux_test_check_received_data;
+  engine_callbacks.appsink_eos = hlsdemux_test_check_no_data_received;
+  engine_callbacks.bus_error_message = testDownloadErrorMessageCallback;
+
+  gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
+  gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
+      "http://unit.test/master.m3u8", &engine_callbacks, engineTestData);
+
+  TESTCASE_UNREF_BOILERPLATE;
+}
+
+GST_END_TEST;
+
+/* work-around that adaptivedemux is not posting an error message
+   about failure to download a fragment */
+static void
+missing_message_eos_callback (GstAdaptiveDemuxTestEngine * engine,
+    GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data)
+{
+  GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data);
+  GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData;
+
+  fail_unless (stream != NULL);
+  testOutputStreamData =
+      gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, NULL);
+  fail_unless (testOutputStreamData != NULL);
+  /* expect to receive less than file size */
+  fail_unless (stream->total_received_size <
+      testOutputStreamData->expected_size,
+      "size validation failed for %s, expected < %d received %d",
+      testOutputStreamData->name, testOutputStreamData->expected_size,
+      stream->total_received_size);
+  testData->count_of_finished_streams++;
+  GST_DEBUG ("EOS callback %d %d",
+      testData->count_of_finished_streams,
+      g_list_length (testData->output_streams));
+  if (testData->count_of_finished_streams ==
+      g_list_length (testData->output_streams)) {
+    g_main_loop_quit (engine->loop);
+  }
+}
+
+
+/*
+ * Test fragment download error
+ * Let the adaptive demux download a few bytes, then instruct the
+ * test soup http src element to generate an error.
+ */
+GST_START_TEST (testFragmentDownloadError)
+{
+  const guint segment_size = 30 * TS_PACKET_LEN;
+  const gchar *master_playlist =
+      "#EXTM3U\n"
+      "#EXT-X-VERSION:4\n"
+      "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1251135, CODECS=\"avc1.42001f mp4a.40.2\", RESOLUTION=640x352\n"
+      "1200.m3u8\n";
+  const gchar *media_playlist =
+      "#EXTM3U \n"
+      "#EXT-X-VERSION:4\n"
+      "#EXT-X-TARGETDURATION:1\n"
+      "#EXTINF:1,Test\n" "001.ts\n"
+      "#EXTINF:1,Test\n" "002.ts\n" "#EXT-X-ENDLIST\n";
+  GstHlsDemuxTestInputData inputTestData[] = {
+    {"http://unit.test/master.m3u8", (guint8 *) master_playlist, 0},
+    {"http://unit.test/1200.m3u8", (guint8 *) media_playlist, 0},
+    {"http://unit.test/001.ts", NULL, segment_size},
+    {"http://unit.test/002.ts", NULL, segment_size},
+    {NULL, NULL, 0}
+  };
+  GstAdaptiveDemuxTestExpectedOutput outputTestData[] = {
+    {"src_0", 2 * segment_size, NULL},
+    {NULL, 0, NULL}
+  };
+  const guint64 failure_position = 2048;
+  TESTCASE_INIT_BOILERPLATE (segment_size);
+
+  http_src_callbacks.src_start = gst_hlsdemux_test_src_start;
+  http_src_callbacks.src_create = gst_hlsdemux_test_network_error_src_create;
+  gst_structure_set (hlsTestCase.state,
+      "failure-suffix", G_TYPE_STRING, "001.ts",
+      "failure-position", G_TYPE_UINT64, failure_position, NULL);
+  engine_callbacks.appsink_received_data =
+      gst_adaptive_demux_test_check_received_data;
+  engine_callbacks.appsink_eos = missing_message_eos_callback;
+  engine_callbacks.bus_error_message = testDownloadErrorMessageCallback;
+
+  gst_test_http_src_install_callbacks (&http_src_callbacks, &hlsTestCase);
+  gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME,
+      inputTestData[0].uri, &engine_callbacks, engineTestData);
+
+  TESTCASE_UNREF_BOILERPLATE;
+}
+
+GST_END_TEST;
+
+static Suite *
+hls_demux_suite (void)
+{
+  Suite *s = suite_create ("hls_demux");
+  TCase *tc_basicTest = tcase_create ("basicTest");
+
+  tcase_add_test (tc_basicTest, simpleTest);
+  tcase_add_test (tc_basicTest, testMasterPlaylist);
+  tcase_add_test (tc_basicTest, testMediaPlaylistNotFound);
+  tcase_add_test (tc_basicTest, testFragmentNotFound);
+  tcase_add_test (tc_basicTest, testFragmentDownloadError);
+  tcase_add_test (tc_basicTest, testSeek);
+
+  tcase_add_unchecked_fixture (tc_basicTest, gst_adaptive_demux_test_setup,
+      gst_adaptive_demux_test_teardown);
+
+  suite_add_tcase (s, tc_basicTest);
+
+  return s;
+}
+
+GST_CHECK_MAIN (hls_demux);