From be19a96112fb721db9c2a5e508a5dfbec180f5b3 Mon Sep 17 00:00:00 2001 From: Hajto Date: Fri, 7 Dec 2018 17:50:35 +0100 Subject: [PATCH] Renamespaced testing generic testing utilities. --- .gitignore | 3 +- .../testing_configurable_pipeline.ex | 102 ------------- .../data_source.ex} | 8 +- .../testing_event.ex => testing/event.ex} | 2 +- lib/membrane/testing/pipeline.ex | 143 ++++++++++++++++++ .../pipeline}/assertions.ex | 8 +- .../testing_sink.ex => testing/sink.ex} | 7 +- .../testing_source.ex => testing/source.ex} | 19 +-- mix.exs | 3 +- mix.lock | 2 + .../core/element/action_handler_spec.exs | 4 +- spec/membrane/core/pull_buffer_spec.exs | 4 +- .../core/element/action_handler_test.exs | 4 +- test/membrane/demands_test.exs | 28 ++-- .../support/demand_test/filter.ex | 2 +- .../pipeline.ex} | 2 +- 16 files changed, 191 insertions(+), 150 deletions(-) delete mode 100644 lib/membrane/integration/testing_configurable_pipeline.ex rename lib/membrane/{integration/testing_data_source.ex => testing/data_source.ex} (78%) rename lib/membrane/{integration/testing_event.ex => testing/event.ex} (54%) create mode 100644 lib/membrane/testing/pipeline.ex rename lib/membrane/{integration/testing_configurable_pipeline => testing/pipeline}/assertions.ex (68%) rename lib/membrane/{integration/testing_sink.ex => testing/sink.ex} (83%) rename lib/membrane/{integration/testing_source.ex => testing/source.ex} (61%) rename lib/membrane/integration/testing_filter.ex => test/support/demand_test/filter.ex (94%) rename test/support/{testing_pipeline.ex => demand_test/pipeline.ex} (93%) diff --git a/.gitignore b/.gitignore index 529f565ba..0b26e5350 100644 --- a/.gitignore +++ b/.gitignore @@ -88,7 +88,7 @@ erl_crash.dump .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* @@ -127,7 +127,6 @@ tags !.vscode/launch.json !.vscode/extensions.json .history -.vscode/settings.json ### Windows ### # Windows thumbnail cache files diff --git a/lib/membrane/integration/testing_configurable_pipeline.ex b/lib/membrane/integration/testing_configurable_pipeline.ex deleted file mode 100644 index 00f051da1..000000000 --- a/lib/membrane/integration/testing_configurable_pipeline.ex +++ /dev/null @@ -1,102 +0,0 @@ -defmodule Membrane.Integration.TestingConfigurablePipeline do - @moduledoc """ - Provides a testing utility pipeline which can be easily configured from test - """ - use Membrane.Pipeline - - alias Membrane.Element - alias Membrane.Pipeline.Spec - - @doc """ - Shortcut for building arguments for starting `TestingConfigurablePipeline`. - - Works only when using standard `input` and `output` pad names. - - ## Examples - - iex> TestingConfigurablePipeline.build_pipeline([el1: MembraneElement1, el2: MembraneElement2], :my_pid) - %{ - elements: [el1: MembraneElement1, el2: MembraneElement2], - links: %{{:output, :el1} => {:input, :el2}}, - test_process: :my_pid - } - """ - - @spec build_pipeline(elements :: Spec.children_spec_t(), pid()) :: map() - def build_pipeline(elements, test_process) do - [first_name | element_names] = Enum.map(elements, fn {name, _} -> name end) - - {links, _} = - Enum.reduce(element_names, {%{}, first_name}, fn x, {links, previous_element} -> - links = Map.put(links, {:output, previous_element}, {:input, x}) - {links, x} - end) - - %{ - elements: elements, - links: links, - test_process: test_process - } - end - - @doc """ - Sends message to a child by Element name. - - ## Examples - - message_child(pipeline, :sink, {:message, "to handle"}) - """ - - @spec message_child(pid(), Element.name_t(), any()) :: {:for_element, atom(), atom()} - def message_child(pipeline, child, message) do - send(pipeline, {:for_element, child, message}) - end - - @impl true - def handle_init(%{ - elements: elements, - links: links, - test_process: test_process_pid - }) do - spec = %Membrane.Pipeline.Spec{ - children: elements, - links: links - } - - {{:ok, spec}, %{test_process: test_process_pid}} - end - - @impl true - def handle_stopped_to_prepared(state), - do: notify_parent(:handle_stopped_to_prepared, state) - - @impl true - def handle_playing_to_prepared(state), - do: notify_parent(:handle_playing_to_prepared, state) - - @impl true - def handle_prepared_to_playing(state), - do: notify_parent(:handle_prepared_to_playing, state) - - @impl true - def handle_prepared_to_stopped(state), - do: notify_parent(:handle_prepared_to_stopped, state) - - @impl true - def handle_notification(notification, from, state), - do: notify_parent({:handle_notification, {notification, from}}, state) - - @impl true - def handle_other({:for_element, element, message}, state) do - {{:ok, forward: {element, message}}, state} - end - - def handle_other(message, state), - do: notify_parent({:handle_other, message}, state) - - defp notify_parent(message, %{test_process: parent} = state) do - send(parent, {__MODULE__, message}) - - {:ok, state} - end -end diff --git a/lib/membrane/integration/testing_data_source.ex b/lib/membrane/testing/data_source.ex similarity index 78% rename from lib/membrane/integration/testing_data_source.ex rename to lib/membrane/testing/data_source.ex index ab377307c..acdcb10f9 100644 --- a/lib/membrane/integration/testing_data_source.ex +++ b/lib/membrane/testing/data_source.ex @@ -1,13 +1,15 @@ -defmodule Membrane.Element.TestingDataSource do +defmodule Membrane.Testing.DataSource do @moduledoc """ - Testing Element for suplying data from Enumerable passed through options. + Testing Element for supplying data from list passed through options. """ + use Membrane.Element.Base.Source alias Membrane.{Buffer, Event} def_options data: [ - type: :buffers + type: :payloads, + spec: [Membrane.Payload.t()] ] def_output_pads output: [ diff --git a/lib/membrane/integration/testing_event.ex b/lib/membrane/testing/event.ex similarity index 54% rename from lib/membrane/integration/testing_event.ex rename to lib/membrane/testing/event.ex index 0a1e159de..abe38497a 100644 --- a/lib/membrane/integration/testing_event.ex +++ b/lib/membrane/testing/event.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Support.TestingEvent do +defmodule Membrane.Testing.Event do @derive Membrane.EventProtocol defstruct [] end diff --git a/lib/membrane/testing/pipeline.ex b/lib/membrane/testing/pipeline.ex new file mode 100644 index 000000000..e58526a8c --- /dev/null +++ b/lib/membrane/testing/pipeline.ex @@ -0,0 +1,143 @@ +defmodule Membrane.Testing.Pipeline do + @moduledoc """ + Provides a testing utility pipeline which can be easily configured from test. + """ + + use Membrane.Pipeline + + alias Membrane.Element + alias Membrane.Pipeline.Spec + + defmodule Options do + @moduledoc """ + Structure representing `options` passed to testing pipeline. + + ## Monitored Callbacks + List of callback names that shall be sent to process in `test_process` field + + ## Test Process + `pid` of process that shall receive messages about Pipeline state. + + ## Elements + List of element specs. + + ## Links + If links is not present or set to nil it will be populated automatically based on elements order using default pad names. + """ + + @enforce_keys [:elements, :test_process] + defstruct @enforce_keys ++ [:monitored_callbacks, :links] + + @type pipeline_callback :: + :handle_notification + | :handle_playing_to_prepared + | :handle_prepared_to_playing + | :handle_prepared_to_stopped + | :handle_stopped_to_prepared + + @type t :: %__MODULE__{ + monitored_callbacks: pipeline_callback(), + test_process: pid(), + elements: Spec.children_spec_t(), + links: Spec.links_spec_t() + } + end + + @doc """ + Links subsequent elements using default pads (linking `:input` to `:output` of previous element). + + ## Examples + + iex> Pipeline.populate_links([el1: MembraneElement1, el2: MembraneElement2], :my_pid) + %{{:output, :el1} => {:input, :el2}} + """ + @spec populate_links(elements :: Spec.children_spec_t()) :: Spec.links_spec_t() + def populate_links(elements) do + [first_name | element_names] = Enum.map(elements, fn {name, _} -> name end) + + {links, _} = + Enum.reduce(element_names, {%{}, first_name}, fn x, {links, previous_element} -> + links = Map.put(links, {:output, previous_element}, {:input, x}) + {links, x} + end) + + links + end + + @doc """ + Sends message to a child by Element name. + + ## Examples + + message_child(pipeline, :sink, {:message, "to handle"}) + """ + @spec message_child(pid(), Element.name_t(), any()) :: :ok + def message_child(pipeline, child, message) do + send(pipeline, {:for_element, child, message}) + :ok + end + + @impl true + def handle_init(options) + + def handle_init(%Options{monitored_callbacks: nil} = options), + do: handle_init(%Options{options | monitored_callbacks: []}) + + def handle_init(%Options{links: nil, elements: elements} = options) do + new_links = populate_links(elements) + %Options{options | links: new_links} + end + + def handle_init(args) do + %Options{elements: elements, links: links} = args + + spec = %Membrane.Pipeline.Spec{ + children: elements, + links: links + } + + new_state = Map.take(args, [:monitored_callbacks, :test_process]) + {{:ok, spec}, new_state} + end + + @impl true + def handle_stopped_to_prepared(state), + do: notify_parent(:handle_stopped_to_prepared, state) + + @impl true + def handle_playing_to_prepared(state), + do: notify_parent(:handle_playing_to_prepared, state) + + @impl true + def handle_prepared_to_playing(state), + do: notify_parent(:handle_prepared_to_playing, state) + + @impl true + def handle_prepared_to_stopped(state), + do: notify_parent(:handle_prepared_to_stopped, state) + + @impl true + def handle_notification(notification, from, state), + do: notify_parent({:handle_notification, {notification, from}}, state) + + @impl true + def handle_other({:for_element, element, message}, state) do + {{:ok, forward: {element, message}}, state} + end + + def handle_other(message, state), + do: notify_parent({:handle_other, message}, state) + + defp get_callback_name(name) when is_atom(name), do: name + defp get_callback_name({name, _}), do: name + + defp notify_parent(message, state) do + %{test_process: parent, monitored_callbacks: monitored_callbacks} = state + + if get_callback_name(message) in monitored_callbacks do + send(parent, {__MODULE__, message}) + end + + {:ok, state} + end +end diff --git a/lib/membrane/integration/testing_configurable_pipeline/assertions.ex b/lib/membrane/testing/pipeline/assertions.ex similarity index 68% rename from lib/membrane/integration/testing_configurable_pipeline/assertions.ex rename to lib/membrane/testing/pipeline/assertions.ex index b58532ab0..36747c2f6 100644 --- a/lib/membrane/integration/testing_configurable_pipeline/assertions.ex +++ b/lib/membrane/testing/pipeline/assertions.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Integration.TestingConfigurablePipeline.Assertions do +defmodule Membrane.Testing.Pipeline.Assertions do @doc """ Asserts that a message matching `pattern` was or is going to be received within the `timeout` period, specified in milliseconds. @@ -6,15 +6,13 @@ defmodule Membrane.Integration.TestingConfigurablePipeline.Assertions do The `pattern` argument must be a match pattern. Flunks with `failure_message` if a message matching `pattern` is not received. """ - defmacro receive_message( + defmacro assert_receive_message( pattern, timeout \\ Application.fetch_env!(:ex_unit, :assert_receive_timeout), failure_message \\ nil ) do - import ExUnit.Assertions, only: [assert_receive: 1] - quote do - assert_receive {Membrane.Integration.TestingConfigurable.Pipeline, unquote(pattern)}, + assert_receive {Membrane.Testing.Pipeline, unquote(pattern)}, unquote(timeout), unquote(failure_message) end diff --git a/lib/membrane/integration/testing_sink.ex b/lib/membrane/testing/sink.ex similarity index 83% rename from lib/membrane/integration/testing_sink.ex rename to lib/membrane/testing/sink.ex index 0b6350c33..d1a0065ba 100644 --- a/lib/membrane/integration/testing_sink.ex +++ b/lib/membrane/testing/sink.ex @@ -1,7 +1,8 @@ -defmodule Membrane.Integration.TestingSink do +defmodule Membrane.Testing.Sink do @moduledoc """ - Sink Element that will send every buffer it receive to pid passed as argument. + Sink Element that will send every buffer it receives to pid passed as argument. """ + use Membrane.Element.Base.Sink def_input_pads input: [demand_unit: :buffers, caps: :any] @@ -14,7 +15,7 @@ defmodule Membrane.Integration.TestingSink do type: :boolean, default: true, description: - "If true element will automatically place demands, otherwise it will be triggered by `:make_demand` message." + "If true element will automatically place demands. If it is set to false demand has to be triggered manually by sending `:make_demand` message." ] @impl true diff --git a/lib/membrane/integration/testing_source.ex b/lib/membrane/testing/source.ex similarity index 61% rename from lib/membrane/integration/testing_source.ex rename to lib/membrane/testing/source.ex index 59ff0ec60..a9cbb2b24 100644 --- a/lib/membrane/integration/testing_source.ex +++ b/lib/membrane/testing/source.ex @@ -1,9 +1,8 @@ -defmodule Membrane.Integration.TestingSource do +defmodule Membrane.Testing.Source do @moduledoc """ - Testing Element for suplying data based on generator function passed through options. - - Generator function must take two arguments (`counter` and `size`) and return buffer with payload of size `size`. + Testing Element for supplying data based on generator function passed through options. """ + use Membrane.Element.Base.Source alias Membrane.Buffer use Bunch @@ -13,7 +12,9 @@ defmodule Membrane.Integration.TestingSource do def_options actions_generator: [ type: :function, spec: (non_neg_integer, non_neg_integer -> [Membrane.Action.t()]), - default: &__MODULE__.default_buf_gen/2 + default: &__MODULE__.default_buf_gen/2, + description: + "Action generator takes two arguments. First is counter which is incremented by 1 every call and second argument represents size of demand." ] @impl true @@ -22,13 +23,7 @@ defmodule Membrane.Integration.TestingSource do end @impl true - def handle_demand( - :output, - size, - :buffers, - _ctx, - %{cnt: cnt} = state - ) do + def handle_demand(:output, size, :buffers, _ctx, %{cnt: cnt} = state) do {actions, cnt} = state.actions_generator.(cnt, size) {{:ok, actions}, %{state | cnt: cnt}} diff --git a/mix.exs b/mix.exs index 578983e0e..90a2166d4 100644 --- a/mix.exs +++ b/mix.exs @@ -62,7 +62,8 @@ defmodule Membrane.Mixfile do {:espec, "~> 1.6", only: :test}, {:excoveralls, "~> 0.8", only: :test}, {:qex, "~> 0.3"}, - {:bunch, "~> 0.1.2"} + {:bunch, "~> 0.1.2"}, + {:dialyxir, "~> 1.0.0-rc.4", only: [:dev], runtime: false} ] end end diff --git a/mix.lock b/mix.lock index 597afe102..69fe485a2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,9 @@ %{ "bunch": {:hex, :bunch, "0.1.2", "2389a4bcdb62382fbfeed9e19952cc22452dc0c01b4bcac941cce00ef212d7b4", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, + "erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"}, "espec": {:hex, :espec, "1.6.3", "d9355788e508b82743a1b1b9aa5ac64ba37b0547c6210328d909e8a6eb56d42e", [:mix], [{:meck, "0.8.12", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "excoveralls": {:hex, :excoveralls, "0.10.2", "fb4abd5b8a1b9d52d35e1162e7e2ea8bfb84b47ae07c38d39aa8ce64be0b0794", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/spec/membrane/core/element/action_handler_spec.exs b/spec/membrane/core/element/action_handler_spec.exs index fb25e68fa..22dcf695e 100644 --- a/spec/membrane/core/element/action_handler_spec.exs +++ b/spec/membrane/core/element/action_handler_spec.exs @@ -3,7 +3,7 @@ defmodule Membrane.Core.Element.ActionHandlerSpec do alias Membrane.Buffer alias Membrane.Core.{Element, Message, Playback} alias Element.State - alias Membrane.Support.TestingEvent + alias Membrane.Testing.Event alias Membrane.Support.Element.{TrivialFilter, TrivialSource} require Message @@ -224,7 +224,7 @@ defmodule Membrane.Core.Element.ActionHandlerSpec do let :pad_ref, do: :output let :payload, do: <<1, 2, 3, 4, 5>> - let :event, do: %TestingEvent{} + let :event, do: %Event{} context "when element is in a 'playing' state" do let :playback, do: %Playback{state: :playing} diff --git a/spec/membrane/core/pull_buffer_spec.exs b/spec/membrane/core/pull_buffer_spec.exs index 75034e84e..21750744e 100644 --- a/spec/membrane/core/pull_buffer_spec.exs +++ b/spec/membrane/core/pull_buffer_spec.exs @@ -1,6 +1,6 @@ defmodule Membrane.Core.PullBufferSpec do alias Membrane.Core.{PullBuffer, Message} - alias Membrane.Support.TestingEvent + alias Membrane.Testing.Event require Message alias Membrane.Buffer use ESpec, async: true @@ -145,7 +145,7 @@ defmodule Membrane.Core.PullBufferSpec do context "when `type` is :event" do let :type, do: :event - let :v, do: %TestingEvent{} + let :v, do: %Event{} it "should append event to the queue" do {:ok, %{q: new_q}} = described_module().store(pb(), type(), v()) diff --git a/test/membrane/core/element/action_handler_test.exs b/test/membrane/core/element/action_handler_test.exs index a9dd41cae..983ce09d0 100644 --- a/test/membrane/core/element/action_handler_test.exs +++ b/test/membrane/core/element/action_handler_test.exs @@ -1,6 +1,6 @@ defmodule Membrane.Core.Element.ActionHandlerTest do use ExUnit.Case, async: false - alias Membrane.Integration.TestingFilter + alias Membrane.DemandTest.Filter alias Membrane.Core.{Element, Playback} alias Element.State @@ -8,7 +8,7 @@ defmodule Membrane.Core.Element.ActionHandlerTest do setup do state = %{ - State.new(TestingFilter, :test_name) + State.new(Filter, :test_name) | watcher: self(), type: :filter, pads: %{ diff --git a/test/membrane/demands_test.exs b/test/membrane/demands_test.exs index 8758d58fc..9c14e5239 100644 --- a/test/membrane/demands_test.exs +++ b/test/membrane/demands_test.exs @@ -1,7 +1,9 @@ defmodule Membrane.Integration.DemandsTest do use ExUnit.Case, async: false use Bunch - alias Membrane.Integration.{TestingFilter, TestingSource, TestingSink, TestingPipeline} + alias Membrane.DemandTest + alias DemandTest.Filter + alias Membrane.Testing.{Source, Sink} alias Membrane.Pipeline # Asserts that message equal to pattern will be received within 200ms @@ -44,10 +46,10 @@ defmodule Membrane.Integration.DemandsTest do test "Regular pipeline with proper demands" do assert {:ok, pid} = - Pipeline.start_link(TestingPipeline, %{ - source: TestingSource, - filter: TestingFilter, - sink: %TestingSink{target: self(), autodemand: false}, + Pipeline.start_link(DemandTest.Pipeline, %{ + source: Source, + filter: Filter, + sink: %Sink{target: self(), autodemand: false}, target: self() }) @@ -58,10 +60,10 @@ defmodule Membrane.Integration.DemandsTest do filter_demand_gen = fn _ -> 2 end assert {:ok, pid} = - Pipeline.start_link(TestingPipeline, %{ - source: TestingSource, - filter: %TestingFilter{demand_generator: filter_demand_gen}, - sink: %TestingSink{target: self(), autodemand: false}, + Pipeline.start_link(DemandTest.Pipeline, %{ + source: Source, + filter: %Filter{demand_generator: filter_demand_gen}, + sink: %Sink{target: self(), autodemand: false}, target: self() }) @@ -83,10 +85,10 @@ defmodule Membrane.Integration.DemandsTest do end assert {:ok, pid} = - Pipeline.start_link(TestingPipeline, %{ - source: %TestingSource{actions_generator: actions_gen}, - filter: TestingFilter, - sink: %TestingSink{target: self(), autodemand: false}, + Pipeline.start_link(DemandTest.Pipeline, %{ + source: %Source{actions_generator: actions_gen}, + filter: Filter, + sink: %Sink{target: self(), autodemand: false}, target: self() }) diff --git a/lib/membrane/integration/testing_filter.ex b/test/support/demand_test/filter.ex similarity index 94% rename from lib/membrane/integration/testing_filter.ex rename to test/support/demand_test/filter.ex index f1d4a900c..a753c09cf 100644 --- a/lib/membrane/integration/testing_filter.ex +++ b/test/support/demand_test/filter.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Integration.TestingFilter do +defmodule Membrane.DemandTest.Filter do use Membrane.Element.Base.Filter alias Membrane.Buffer diff --git a/test/support/testing_pipeline.ex b/test/support/demand_test/pipeline.ex similarity index 93% rename from test/support/testing_pipeline.ex rename to test/support/demand_test/pipeline.ex index 353c8ba74..aba25b057 100644 --- a/test/support/testing_pipeline.ex +++ b/test/support/demand_test/pipeline.ex @@ -1,4 +1,4 @@ -defmodule Membrane.Integration.TestingPipeline do +defmodule Membrane.DemandTest.Pipeline do use Membrane.Pipeline @impl true