Packages
membrane_core
0.3.2
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.7
1.2.6
1.2.5
retired
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc1
1.1.2
1.1.1
1.1.0
1.1.0-rc1
1.1.0-rc0
1.0.1
1.0.0
1.0.0-rc1
1.0.0-rc0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
retired
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
Membrane Multimedia Framework (Core)
Current section
Files
Jump to
Current section
Files
lib/membrane/testing/pipeline.ex
defmodule Membrane.Testing.Pipeline do
@moduledoc """
This Pipeline was created to reduce testing boilerplate and ease communication with it's elements.
It also provides utility for receiving messages when `Pipeline` playback state changes
and notifications it receives.
## Starting Pipeline
When you want a build Pipeline to test your elements you need three things:
- Pipeline Module
- List of elements
- Links between those elements
When creating pipelines for tests the only essential part is the list of elements.
In most cases during the tests elements are linked in a way that
`:output` pad is linked to `:input` pad of subsequent element.
So we only need to pass a list of elements and links can be generated automatically.
To start a testing pipeline you need to build `Membrane.Testing.Pipeline.Options` struct
and pass to to `Membrane.Testing.Pipeline.start_link/2`.
```
options = %Membrane.Testing.Pipeline.Options {
elements: [
el1: MembraneElement1,
el2: MembraneElement2,
...
]
}
{:ok, pipeline} = Membrane.Testing.Pipeline.start_link(options)
```
See `Membrane.Testing.Pipeline.Options` for available options.
Links are generated by `populate_links/1`.
## Receiving notifications about callbacks invocation
In some cases, you want to get a notification when `Pipeline` reaches a certain state.
You can achieve that by monitoring pipeline callbacks.
```
options = %Membrane.Testing.Pipeline.Options {
monitored_callbacks: [:handle_prepared_to_playing],
test_process: pid
...
}
```
First, you need to configure which callbacks are to be monitored by putting their names
into `monitored_callbacks` field of `Options` struct. Check `t:Membrane.Testing.Pipeline.Options.pipeline_callback/0`
for list of available callback names.
You also need to pass a `PID` of the process that will receive messages.
```
import Membrane.Testing.Pipeline.Assertions
assert_receive_message :handle_prepared_to_playing
```
Then it is a matter of waiting for the right message to come.
You can do that easily by using `Membrane.Testing.Pipeline.Assertions.assert_receive_message/3`.
## Messaging children
You can send messages to children using their names specified in the elements list.
Please check `message_child/3` for more details.
"""
use Membrane.Pipeline
alias Membrane.Element
alias Membrane.Pipeline.Spec
defmodule Options do
@moduledoc """
Structure representing `options` passed to testing pipeline.
## Monitored Callbacks
A message will be sent to `test process` if the invoked callback is in the list of monitored callbacks.
See `t:pipeline_callback/0` for available callbacks.
## Test Process
`pid` of process that shall receive messages when Pipeline invokes playback state change callback
and receives notification.
## Elements
List of element specs.
## Links
Map describing links between elements.
If links are not present or set to nil they will be populated automatically based on elements order
using default pad names.
"""
@enforce_keys [:elements]
defstruct @enforce_keys ++ [:monitored_callbacks, :links, :test_process]
@typedoc """
Defines supported callback names.
"""
@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() | nil,
test_process: pid() | nil,
elements: Spec.children_spec_t(),
links: Spec.links_spec_t() | nil
}
end
@doc """
Links subsequent elements using default pads (linking `:input` to `:output` of previous element).
## Examples
iex> Pipeline.populate_links([el1: MembraneElement1, el2: MembraneElement2])
%{{:el1, :output} => {:el2, :input}}
"""
@spec populate_links(elements :: Spec.children_spec_t()) :: Spec.links_spec_t()
def populate_links(elements) do
elements
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [{output_name, _}, {input_name, _}] ->
{{output_name, :output}, {input_name, :input}}
end)
|> Enum.into(%{})
end
@doc """
Sends message to a child by Element name.
## Examples
Knowing that `pipeline` has child named `sink`, message can be sent as follows:
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{test_process: nil, monitored_callbacks: list})
when is_list(list) and length(list) > 0,
do: {:error, :no_pid_when_monitoring}
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)
handle_init(%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