From d7f5a8e441505a7b42346a12019fed777567cdde Mon Sep 17 00:00:00 2001 From: Alex McLain Date: Thu, 2 Jul 2020 12:05:22 -0700 Subject: [PATCH 1/3] Add filter to be able to skip tests. --- test/test_helper.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..d13e5fc 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1 @@ -ExUnit.start() +ExUnit.start(exclude: [:skip]) From 16e3fe71388fe52c58fa913b52ec70da918d663a Mon Sep 17 00:00:00 2001 From: Alex McLain Date: Thu, 2 Jul 2020 12:05:43 -0700 Subject: [PATCH 2/3] Remove boilerplate Xebow test file. --- test/xebow_test.exs | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test/xebow_test.exs diff --git a/test/xebow_test.exs b/test/xebow_test.exs deleted file mode 100644 index 3dccb27..0000000 --- a/test/xebow_test.exs +++ /dev/null @@ -1,4 +0,0 @@ -defmodule XebowTest do - use ExUnit.Case - doctest Xebow -end From 00a9e1cc8271f09074e3c7c3168ff8f2927e7ef7 Mon Sep 17 00:00:00 2001 From: Alex McLain Date: Thu, 2 Jul 2020 15:05:11 -0700 Subject: [PATCH 3/3] Add tests for engine. Also supply engine with an initial animation. --- lib/xebow/application.ex | 5 +- lib/xebow/engine.ex | 23 +++++---- test/engine_test.exs | 109 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 test/engine_test.exs diff --git a/lib/xebow/application.ex b/lib/xebow/application.ex index 7e279c5..6914357 100644 --- a/lib/xebow/application.ex +++ b/lib/xebow/application.ex @@ -5,6 +5,9 @@ defmodule Xebow.Application do use Application + @animation_type Xebow.Animation.types() |> List.first() + @animation Xebow.Animation.new(type: @animation_type) + def start(_type, _args) do # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options @@ -36,7 +39,7 @@ defmodule Xebow.Application do # {Xebow.Worker, arg}, Xebow.HIDGadget, Xebow.RGBMatrix, - {Xebow.Engine, [Xebow.RGBMatrix]}, + {Xebow.Engine, {@animation, [Xebow.RGBMatrix]}}, Xebow.Keys ] end diff --git a/lib/xebow/engine.ex b/lib/xebow/engine.ex index 3e20f05..bc54dcf 100644 --- a/lib/xebow/engine.ex +++ b/lib/xebow/engine.ex @@ -20,12 +20,19 @@ defmodule Xebow.Engine do @doc """ Start the engine. - This function accepts the following arguments: - - `paintables` - A list of modules that implement the `Xebow.Paintable` behaviour. + This module registers its process globally and is expected to be started by + a supervisor. + + This function accepts the following arguments as a tuple: + - `initial_animation` - The animation that plays when the engine starts. + - `paintables` - A list of modules to output `Xebow.Frame` to that implement + the `Xebow.Paintable` behavior. If you want to register your paintables + dynamically, set this to an empty list `[]`. """ - @spec start_link(paintables :: list(module)) :: GenServer.on_start() - def start_link(paintables \\ []) do - GenServer.start_link(__MODULE__, {paintables}, name: __MODULE__) + @spec start_link({initial_animation :: Animation.t(), paintables :: list(module)}) :: + GenServer.on_start() + def start_link({initial_animation, paintables}) do + GenServer.start_link(__MODULE__, {initial_animation, paintables}, name: __MODULE__) end @doc """ @@ -83,18 +90,16 @@ defmodule Xebow.Engine do # Server @impl GenServer - def init({paintables}) do + def init({initial_animation, paintables}) do send(self(), :get_next_frame) - [initial_animation_type | _] = Animation.types() - animation = Animation.new(type: initial_animation_type) initial_state = %State{paintables: %{}} state = Enum.reduce(paintables, initial_state, fn paintable, state -> add_paintable(paintable, state) end) - |> set_animation(animation) + |> set_animation(initial_animation) {:ok, state} end diff --git a/test/engine_test.exs b/test/engine_test.exs new file mode 100644 index 0000000..07e090f --- /dev/null +++ b/test/engine_test.exs @@ -0,0 +1,109 @@ +defmodule Xebow.EngineTest do + use ExUnit.Case + + alias Xebow.{Animation, Engine, Frame} + + # Creates a Xebow.Paintable module that emits frames to the test suite process. + defp paintable(%{test: test_name}) do + process = self() + module_name = String.to_atom("#{test_name}-paintable") + + Module.create( + module_name, + quote do + def get_paint_fn do + fn frame -> + send(unquote(process), {:frame, frame}) + end + end + end, + Macro.Env.location(__ENV__) + ) + + %{paintable: module_name} + end + + # Creates a single pixel, single frame animation. + defp solid_animation(red \\ 255, green \\ 127, blue \\ 0) do + pixels = [{0, 0}] + color = Chameleon.RGB.new(red, green, blue) + frame = Frame.solid_color(pixels, color) + + animation = + Animation.new( + type: Animation.Static, + frames: [frame], + delay_ms: 10, + loop: 1 + ) + + {animation, frame} + end + + setup [:paintable] + + test "renders a solid animation", %{paintable: paintable} do + {animation, frame} = solid_animation() + + start_supervised!({Engine, {animation, [paintable]}}) + + assert_receive {:frame, ^frame} + end + + test "renders a multi-frame, multi-pixel animation", %{paintable: paintable} do + pixels = [ + {0, 0}, + {0, 1}, + {1, 0}, + {1, 1} + ] + + frames = [ + Frame.solid_color(pixels, Chameleon.Keyword.new("red")), + Frame.solid_color(pixels, Chameleon.Keyword.new("green")), + Frame.solid_color(pixels, Chameleon.Keyword.new("blue")), + Frame.solid_color(pixels, Chameleon.Keyword.new("white")) + ] + + animation = + Animation.new( + type: Animation.Static, + frames: frames, + delay_ms: 10, + loop: 1 + ) + + start_supervised!({Engine, {animation, [paintable]}}) + + Enum.each(frames, fn frame -> + assert_receive {:frame, ^frame} + end) + end + + test "can play a different animation", %{paintable: paintable} do + {animation, _frame} = solid_animation() + {animation_2, frame_2} = solid_animation(127, 127, 127) + + start_supervised!({Engine, {animation, [paintable]}}) + + :ok = Engine.play_animation(animation_2) + + assert_receive {:frame, ^frame_2} + end + + test "can register and unregister paintables", %{paintable: paintable} do + {animation, frame} = solid_animation() + {animation_2, frame_2} = solid_animation(127, 127, 127) + + start_supervised!({Engine, {animation, []}}) + + :ok = Engine.register_paintable(paintable) + + assert_receive {:frame, ^frame} + + :ok = Engine.unregister_paintable(paintable) + :ok = Engine.play_animation(animation_2) + + refute_receive {:frame, ^frame_2} + end +end