From: Jens Georg Date: Fri, 5 Oct 2012 07:27:28 +0000 (+0200) Subject: test: Add a test for media engines X-Git-Tag: RYGEL_0_17_0~4 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=97b9b6ac76d6891ff469e99d4555959c417b21c8;p=profile%2Fivi%2Frygel.git test: Add a test for media engines --- diff --git a/tests/Makefile.am b/tests/Makefile.am index 65ca376..662724c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -43,10 +43,13 @@ check_PROGRAMS = rygel-http-item-uri-test \ rygel-searchable-container-test \ rygel-item-creator-test \ rygel-user-config-test \ - rygel-regression + rygel-regression \ + rygel-media-engine-test TESTS = $(check_PROGRAMS) +EXTRA_DIST = $(srcdir)/data/test-data.dat + rygel_http_item_uri_test_SOURCES = rygel-http-item-uri-test.vala \ rygel-http-item-uri.vala @@ -131,6 +134,31 @@ rygel_regression_LDADD = \ $(top_builddir)/src/librygel-server/librygel-server-1.0.la \ $(top_builddir)/src/librygel-core/librygel-core-1.0.la +rygel_media_engine_test_SOURCES = \ + rygel-media-engine-test.vala + +rygel_media_engine_test_VALAFLAGS = \ + $(AM_VALAFLAGS) \ + --pkg rygel-server-1.0 \ + --pkg rygel-core-1.0 \ + --vapidir $(top_builddir)/src/librygel-server \ + --vapidir $(top_builddir)/src/librygel-core + +rygel_media_engine_test_CFLAGS = \ + $(AM_CFLAGS) \ + -DTEST_DATA_FOLDER='"$(abs_srcdir)/data"' \ + -DTEST_ENGINE_PATH='"$(abs_top_builddir)/src/media-engines"' \ + -DBUILT_ENGINES='"@BUILT_ENGINES@"' \ + -I$(top_builddir)/src/librygel-server \ + -I$(top_srcdir)/src/librygel-server \ + -I$(top_builddir)/src/librygel-core \ + -I$(top_srcdir)/src/librygel-core + +rygel_media_engine_test_LDADD = \ + $(LDADD) \ + $(top_builddir)/src/librygel-server/librygel-server-1.0.la \ + $(top_builddir)/src/librygel-core/librygel-core-1.0.la + if HAVE_GSTREAMER check_PROGRAMS += \ rygel-playbin-renderer-test \ diff --git a/tests/data/test-data.dat b/tests/data/test-data.dat new file mode 100644 index 0000000..a3a2a5c Binary files /dev/null and b/tests/data/test-data.dat differ diff --git a/tests/rygel-media-engine-test.vala b/tests/rygel-media-engine-test.vala new file mode 100644 index 0000000..6345a2a --- /dev/null +++ b/tests/rygel-media-engine-test.vala @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2012 Intel Corporation. + * + * Author: Jens Georg + * + * This file is part of Rygel. + * + * Rygel is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Rygel 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +[CCode (cname="TEST_DATA_FOLDER")] +extern const string TEST_DATA_FOLDER; + +// Always test locally built engines +[CCode (cname="TEST_ENGINE_PATH")] +extern const string TEST_ENGINE_PATH; + +[CCode (cname="BUILT_ENGINES")] +extern const string BUILT_ENGINES; + +/** + * Helper class to convince the engine loader to load the media engine we want + * to test. + */ +internal class Rygel.DataSourceTestConfig : Rygel.BaseConfiguration { + private string engine; + private string path; + + public DataSourceTestConfig (string? path = null, + string? engine = null) { + this.engine = engine; + this.path = path; + } + + public override string get_media_engine () throws Error { + if (this.engine != null) { + return this.engine; + } + + // Throw error + return base.get_media_engine (); + } + + public override string get_engine_path () throws Error { + if (this.path != null) { + return this.path; + } + + return TEST_ENGINE_PATH; + } + + public string to_string () { + return "Path: %s, Engine: %s".printf (this.path, this.engine); + } + + public void clear () { + this.engine = null; + this.path = null; + } +} + +/** + * Stub implementation of Rygel.HTTPSeek + */ +internal class Rygel.ByteSeek : Rygel.HTTPSeek { + public ByteSeek (int64 first, int64 last, int64 length) { + var msg = new Soup.Message ("GET", "http://example.com/"); + base (msg, first, last, 1, length); + this.seek_type = HTTPSeekType.BYTE; + } + + public override void add_response_headers () {} +} + +/** + * Wrapper class arount uint8[] arrays to help stuff those buffers into a + * Gee.ArrayList + */ +class DataBlock { + public uint8[] data; + + public DataBlock (uint8[] data) { + this.data = data; + } +} + +/** + * Helper class to collect a number of byte buffers + */ +internal class Rygel.DataPool : Gee.ArrayList { + public uint8[] flatten () { + var size = this.total_size (); + var result = new uint8[size]; + var offset = 0; + + foreach (var data in this) { + Memory.copy (result + offset, (void *) data.data, data.data.length); + offset += data.data.length; + } + + this.clear (); + this.add (new DataBlock (result)); + + return result; + } + + public uint64 total_size () { + uint64 total = 0; + foreach (var data in this) { + total += data.data.length; + } + + return total; + } +} + +/** + * Test a DataSource implementation against the expectations of the interface + * + * It is run as part of the test suite but can be used to check arbitrary + * media engines as well: + * + * rygel-media-engine-test /path/to/my/first/custom-rygel-engine.so \ + * /path/to/my/second/custom-rygel-engine.so ... + */ +public class Rygel.DataSourceTest : Object { + private File test_data_file; + private MappedFile test_data_mapped; + + public DataSourceTest () { + var path = Path.build_filename (TEST_DATA_FOLDER, "test-data.dat"); + this.test_data_file = File.new_for_path (path); + try { + this.test_data_mapped = new MappedFile (path, false); + } catch (Error error) { + warning ("Error: Could not map file: %s", error.message); + assert_not_reached (); + } + } + + /// Get the whole file + private void test_simple_streaming () { + debug ("test_simple_streaming"); + var source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + // Sources should support file:// urls + assert (source != null); + + uint64 received_bytes = 0; + var loop = new MainLoop (null, false); + source.data_available.connect ( (data) => { + received_bytes += data.length; + }); + source.done.connect ( (data) => { + loop.quit (); + }); + Idle.add ( () => { source.start (null); return false; }); + loop.run (); + assert (received_bytes == this.test_data_mapped.get_length ()); + source.stop (); + source = null; + } + + /// Simple byte range request tests + private void test_byte_range_request () { + debug ("test_byte_range_request"); + var source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + // Sources should support file:// urls + assert (source != null); + + try { + // Get the first 10 bytes + var seek = new ByteSeek (0, 9, this.test_data_mapped.get_length ()); + + var received_data = new DataPool (); + var loop = new MainLoop (null, false); + source.data_available.connect ( (data) => { + received_data.add (new DataBlock (data)); + }); + source.done.connect ( (data) => { + loop.quit (); + }); + source.error.connect ( () => { assert_not_reached (); }); + source.start (seek); + loop.run (); + assert (received_data.total_size () == 10); + Memory.cmp (this.test_data_mapped.get_contents (), + received_data.flatten (), + (size_t) received_data.total_size ()); + + // Get last 10 bytes + seek = new ByteSeek (this.test_data_mapped.get_length () - 10, + this.test_data_mapped.get_length () - 1, + this.test_data_mapped.get_length ()); + + received_data = new DataPool (); + loop = new MainLoop (null, false); + + source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + source.data_available.connect ( (data) => { + received_data.add (new DataBlock (data)); + }); + source.done.connect ( (data) => { + loop.quit (); + }); + source.error.connect ( () => { assert_not_reached (); }); + source.start (seek); + loop.run (); + + assert (received_data.total_size () == 10); + Memory.cmp (this.test_data_mapped.get_contents () + + (this.test_data_mapped.get_length () - 10), + received_data.flatten (), + (size_t) received_data.total_size ()); + + // Get something from the middle + seek = new ByteSeek (this.test_data_mapped.get_length () / 2, + (this.test_data_mapped.get_length () / 2) + 9, + this.test_data_mapped.get_length ()); + + received_data = new DataPool (); + loop = new MainLoop (null, false); + + source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + source.data_available.connect ( (data) => { + received_data.add (new DataBlock (data)); + }); + + source.done.connect ( (data) => { + loop.quit (); + }); + source.error.connect ( () => { assert_not_reached (); }); + source.start (seek); + loop.run (); + + assert (received_data.total_size () == 10); + Memory.cmp (this.test_data_mapped.get_contents () + + (this.test_data_mapped.get_length () / 2), + received_data.flatten (), + (size_t) received_data.total_size ()); + source.stop (); + source = null; + } catch (DataSourceError.SEEK_FAILED seek_error) { + debug ("Skipping seek test"); + } catch (Error error) { + warning ("Failed to test: %s", error.message); + assert_not_reached (); + } + } + + // Check that calling start() after stop() starts at the beginning of the + // data + private void test_stop_start () { + debug ("test_stop_start"); + var source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + // Sources should support file:// urls + assert (source != null); + + var pool = new DataPool (); + var loop = new MainLoop (null, false); + source.data_available.connect ( (data) => { + pool.add (new DataBlock (data)); + source.stop (); + }); + source.done.connect ( (data) => { + loop.quit (); + }); + Idle.add ( () => { source.start (null); return false; }); + loop.run (); + pool.clear (); + Idle.add ( () => { source.start (null); return false; }); + loop.run (); + Memory.cmp (this.test_data_mapped.get_contents (), + pool.flatten (), + (size_t) pool.total_size ()); + + source.stop (); + source = null; + } + + // Check that calling freeze multiple times only needs one thaw to get the + // data again + private void test_multiple_freeze () { + debug ("test_multiple_freeze"); + var source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + // Sources should support file:// urls + assert (source != null); + var available_id = source.data_available.connect ( () => { + assert_not_reached (); + }); + source.start (null); + source.freeze (); + source.freeze (); + var loop = new MainLoop (null, false); + + Timeout.add_seconds (5, () => { + loop.quit (); + + return false; + }); + + loop.run (); + source.disconnect (available_id); + source.data_available.connect ( () => { + loop.quit (); + }); + + var timeout_id = Timeout.add_seconds (5, () => { + assert_not_reached (); + + return false; + }); + + source.thaw (); + loop.run (); + Source.remove (timeout_id); + source.stop (); + } + + // Check that it is possible to call stop() when the source is frozen and + // still get a done() signal + private void test_freeze_stop () { + debug ("test_freeze_stop"); + var source = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + // Sources should support file:// urls + assert (source != null); + + source.start (null); + source.freeze (); + var loop = new MainLoop (null, false); + source.done.connect ( () => { + loop.quit (); + }); + var id = Timeout.add_seconds ( 5, () => { + assert_not_reached (); + }); + Idle.add ( () => { source.stop (); return false; }); + loop.run (); + Source.remove (id); + source.stop (); + source = null; + } + + // Check that it is possible to stream to two targets in parallel + public void test_parallel_streaming () { + debug ("test_parallel_streaming"); + var source1 = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + assert (source1 != null); + // Sources should support file:// urls + var source2 = MediaEngine.get_default ().create_data_source + (this.test_data_file.get_uri ()); + assert (source2 != null); + + source1.start (null); + + var seek = new ByteSeek (0, + (this.test_data_mapped.get_length () / 2), + this.test_data_mapped.get_length ()); + + source2.start (seek); + var loop = new MainLoop (null, false); + var quit = false; + source1.done.connect ( () => { + if (quit) { + loop.quit (); + } else { + quit = true; + } + }); + + source2.done.connect ( () => { + if (quit) { + loop.quit (); + } else { + quit = true; + } + }); + loop.run (); + } + + public int run () { + this.test_simple_streaming (); + this.test_byte_range_request (); + this.test_stop_start (); + this.test_multiple_freeze (); + this.test_freeze_stop (); + this.test_parallel_streaming (); + + return 0; + } + + public static int main (string[] args) { + string[] engines; + + var configs = new Gee.ArrayList (); + + if (args.length > 1) { + foreach (var arg in args) { + File file; + if (args[1].has_prefix ("~")) { + file = File.parse_name (args[1]); + } else { + file = File.new_for_commandline_arg (args[1]); + } + var path = file.get_parent ().get_path (); + var engine = file.get_basename (); + + configs.add (new DataSourceTestConfig (path, engine)); + } + } else { + foreach (var engine in BUILT_ENGINES.split (";")) { + var name = engine + "." + Module.SUFFIX; + configs.add (new DataSourceTestConfig (null, name)); + } + } + + DataSourceTestConfig previous_config = null; + foreach (var config in configs) { + // Invalidate previous config so MetaConfig picks up the + // current one + if (previous_config != null) { + previous_config.clear (); + } + + debug ("=> Executing tests for config %s", config.to_string ()); + MetaConfig.register_configuration (config); + previous_config = config; + + try { + MediaEngine.init (); + } catch (Error error) { + assert_not_reached (); + } + + var test = new DataSourceTest (); + + var result = test.run (); + if (result != 0) { + return result; + } + } + + return 0; + } +}