Packages
vtc
0.16.4
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/test_utils/stream_data_vtc.ex
if Code.ensure_loaded?(StreamData) and Application.get_env(:vtc, :include_test_utils?) do
defmodule Vtc.TestUtils.StreamDataVtc do
@moduledoc """
`StreamData` generators for use in tests that involve custom Ecto types. For use in
property tests.
For this module to be compiled, the following must be set in your config.exs:
```elixir
config :vtc,
include_test_utils?: true
```
Further, this module will not be compiled if `StreamData` is not present as a
loaded dependency.
"""
alias Vtc.Framerate
alias Vtc.Framestamp
alias Vtc.Rates
alias Vtc.Utils.DropFrame
require ExUnit.Assertions
@doc """
Yields rational values as %Ratio{} structs.
## Options
- `numerator`: A static value to use for the numerator of all generated rationals.
Default: nil.
- `denominator`: A static value to use for the denominator of all generated rationals.
Default: nil.
- `positive?`: If true, only positive values (greater than 0) are generated.
Default: `false`.
## Examples
```elixir
property "prop test" do
check all(fraction <- StreamDataVtc.rational()) do
...
end
end
```
"""
@spec rational(numerator: integer(), denominator: pos_integer(), positive?: boolean()) :: StreamData.t(Ratio.t())
def rational(opts \\ []) do
numerator = Keyword.get(opts, :numerator)
denominator = Keyword.get(opts, :denominator)
positive? = Keyword.get(opts, :positive?, false)
numerator_gen = if positive?, do: StreamData.positive_integer(), else: StreamData.integer()
numerator_gen = if is_integer(numerator), do: StreamData.constant(numerator), else: numerator_gen
denominator_gen =
if is_integer(denominator), do: StreamData.constant(denominator), else: StreamData.positive_integer()
{numerator_gen, denominator_gen}
|> StreamData.tuple()
|> StreamData.map(fn {numerator, denominator} ->
Ratio.new(numerator, denominator)
end)
end
@typedoc """
Describes the opts that can be passed to `framerate/1`.
"""
@type framerate_opts() :: [type: :whole | :fractional | :drop | :non_drop]
@doc """
Yields Vtc.Framerates, always yields true-frame or NTSC; never a mixture of the two.
## Options
- `type`: The ntsc value all framerates should be generated with. Can be any of the
following:
- `:whole`: All yielded framerates will be non-ntsc, whole-frame rates. Ex: 24/1
fps.
- `:fractional`: All yielded framerates will be a random non-drop rate.
- `:non_drop`: All yielded framerates will be NTSC, non-drop values.
- `:drop`: All yielded framerates will be NTSC, drop-frame values.
A list of the above options may be passed and each value yielded from this
generator will pick randomly from them.
Defaults to `[:whole, :fractional, :non_drop, :drop]`
## Examples
```elixir
property "prop test" do
check all(framerate <- StreamDataVtc.framerate()) do
...
end
end
```
"""
@spec framerate(framerate_opts()) :: StreamData.t(Framerate.t())
def framerate(opts \\ []) do
ntsc_list =
case Keyword.get(opts, :type, [:whole, :fractional, :non_drop, :drop]) do
ntsc_list when is_list(ntsc_list) -> ntsc_list
ntsc -> [ntsc]
end
ntsc_gen =
ntsc_list
|> Enum.map(&StreamData.constant(&1))
|> StreamData.one_of()
numerator_gen = StreamData.positive_integer()
denominator_gen = StreamData.positive_integer()
drop_mult_gen = StreamData.integer(1..3)
{numerator_gen, denominator_gen, drop_mult_gen, ntsc_gen}
|> StreamData.tuple()
|> StreamData.map(fn
{numerator, _, _, :whole} ->
numerator |> Ratio.new(1) |> Framerate.new!(ntsc: nil)
{numerator, denominator, _, :fractional} ->
numerator |> Ratio.new(denominator) |> Framerate.new!(ntsc: nil)
{_, _, _, :drop} ->
Rates.f29_97_df()
{numerator, _, _, :non_drop} ->
(numerator * 1000) |> Ratio.new(1001) |> Framerate.new!(ntsc: :non_drop)
end)
end
@type framestamp_opts() :: [non_negative?: boolean(), rate: Framerate.t(), rate_opts: framerate_opts()]
@doc """
Yields Vtc.Framestamp values.
## Options
- `non_negative?`: Will only return values greater than or equal to `00:00:00:00`.
- `rate`: A framerate to use for this test. If one is not provided, a random one will
be used.
- `rate_opts`: Opts that should be passed to `framerate/1` when generating the
framerate. Has no effect if `rate` is set.
## Examples
```elixir
property "returns input of negate/1" do
check all(positive <- StreamDataVtc.framestamp(non_negative?: true)) do
negative = Framestamp.minus(positive)
assert Framestamp.abs(positive) == Framestamp.abs(negative)
end
end
```
"""
@spec framestamp(framestamp_opts()) ::
StreamData.t(Framestamp.t())
def framestamp(opts \\ []) do
non_negative? = Keyword.get(opts, :non_negative?, false)
rate = Keyword.get(opts, :rate)
rate_opts = Keyword.get(opts, :rate_opts, [])
frames_gen = StreamData.integer(0..20_736_000)
frames_gen =
if non_negative?, do: StreamData.filter(frames_gen, &(&1 >= 0)), else: StreamData.integer(-20_736_000..20_736_000)
framerate_gen =
case rate do
%Framerate{} -> StreamData.constant(rate)
nil -> framerate(rate_opts)
end
{frames_gen, framerate_gen}
|> StreamData.tuple()
|> StreamData.map(fn {frames, framerate} ->
frames
|> clip_drop_frames(framerate)
|> Framestamp.with_frames!(framerate)
end)
end
# Clips drop frames to the maximum legal value for SMPTE timecode.
@spec clip_drop_frames(integer(), Framerate.t()) :: integer()
defp clip_drop_frames(frames, %{ntsc: :drop} = rate) do
max_frames = DropFrame.max_frames(rate)
if frames >= 0 do
min(frames, max_frames)
else
max(frames, -max_frames)
end
end
defp clip_drop_frames(frames, _), do: frames
@doc """
Yields Vtc.Framestamp.Range values.
## Options
- `rate_opts`: The options to pass to the `framerate/1` generator for this range.
- `stamp_opts`: The options to pass to the `framestamp/1` generators for this range.
- `filter_empty?`: If `true`, filters 0-length ranges from the output.
Default: `false`.
## Examples
```elixir
property "returns input of negate/1" do
check all(positive <- StreamDataVtc.framestamp_range()) do
...
end
end
```
"""
@spec framestamp_range(
rate_opts: framerate_opts(),
stamp_opts: framestamp_opts(),
filter_empty?: boolean()
) :: StreamData.t(Framestamp.Range.t())
def framestamp_range(opts \\ []) do
rate_opts = Keyword.get(opts, :rate_opts, [])
stamp_opts = Keyword.get(opts, :stamp_opts, [])
filter_empty? = Keyword.get(opts, :filter_empty?, false)
value_stream =
StreamData.bind(framerate(rate_opts), fn rate ->
stamp_opts = Keyword.put_new(stamp_opts, :rate, rate)
{framestamp(stamp_opts), framestamp(stamp_opts)}
|> StreamData.tuple()
|> StreamData.map(fn {stamp_in, stamp_out} ->
stamp_in = Enum.min([stamp_in, stamp_out], Framestamp)
stamp_out = Enum.max([stamp_in, stamp_out], Framestamp)
Framestamp.Range.new!(stamp_in, stamp_out)
end)
end)
if filter_empty? do
StreamData.filter(value_stream, fn range ->
duration = Framestamp.Range.duration(range)
not Ratio.eq?(duration.seconds, 0)
end)
else
value_stream
end
end
@doc """
Runs a test, but does not fail if the operation causes a drop-frame overflow
exception to occur.
"""
@spec run_test_rescue_drop_overflow((() -> term())) :: term()
def run_test_rescue_drop_overflow(test_runner) do
test_runner.()
rescue
error in Framestamp.ParseError ->
ExUnit.Assertions.assert(error.reason == :drop_frame_maximum_exceeded)
end
end
end