Skip to content

Commit

Permalink
adding tests for jobs
Browse files Browse the repository at this point in the history
heavy refactoring for the AnimationManager to make it testable
Adding Mox to test the scheduler
Renaming interfaces to make them more clear
formatting the code
Introducing an interface for the job_scheduler, to instead of relying on the one from Quantum to make it easier mockable
Adding optional options to the job so that extra things can be cofitured (like overlap and timezone
Adding CronExpression to the Fledex imports so the sigle_e can be used
adding missing reuse comments
Updating TODO list
  • Loading branch information
a-maze-d committed Apr 14, 2024
1 parent c6d831f commit 6626b53
Show file tree
Hide file tree
Showing 20 changed files with 307 additions and 133 deletions.
5 changes: 5 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@
"requireFiles": [
"test/**/test_helper.exs",
"test/**/*_test.exs"
],
"excludeModules": [
"Circuits.I2C.Nif",
"Circuits.GPIO.Nif"
]

}
]
}
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Even though this library is published, there are things I still want to do befor
end
end
```
- [ ] Create support for a cron-manager (that allows running jobs, like the one we have in the weather station example and thereby no need to handcraft anything) (v0.4)
- [x] Create support for a cron-manager (that allows running jobs, like the one we have in the weather station example and thereby no need to handcraft anything) (v0.4)
- [ ] Create support for a coordinator (that can control individual animations and effects. Effects will have to notify back their state) (v0.5)
- [ ] Clustering
- [ ] create an animation that consumes those (this allows to connect remote livebooks to a physical led strip. CAUTION: protect against loops!) (v0.5)
Expand Down
44 changes: 40 additions & 4 deletions lib/fledex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ defmodule Fledex do
@spec __using__(keyword) :: Macro.t()
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
import Crontab.CronExpression
import Fledex
# import also the Leds and the color name definitions so no namespace are required
import Fledex.Leds
Expand Down Expand Up @@ -196,17 +197,52 @@ defmodule Fledex do
example livebook](5_fledex_weather_example.livemd)):
```elixir
Fledex.Utils.PubSub.simple_broadcast(%{temperature: -15.2})
Fledex.Utils.PubSub.simple_broadcast(%{temperature: -15.2})
```
Each job consists of:
* `name`- a unique name
* `pattern`- a cron pattern (as specified in
[this cheatsheet](https://hexdocs.pm/crontab/cron_notation.html#expressions)).
Note: `Crontab.CronExpression` gets imported and therefore the sigil can directly
be used, i.e. `~e[* * * * * * * *]e`
* `options`- a keyword list with some options. The following options exist:
* `:run_once`- a boolean that indicates whether the job should be run once
at creation time. This can be important, because you might otherwise have
to wait for an extended time before the function will be executed.
* `:timezone`- The timezone the cron pattern applies to. If nothing is specified
`:utc` is assumed
* `:overlap`- This indicates whether jobs should overlap or not. An overlap can
happen when running the job takes more time than the interval between job runs.
For safety reason the default is `false`.
* `:do` - a block of code that should be executed. You can specify directly
your code here. It will be wrapped into an anonymous function.
Example:
```elixir
use Fledex
led_strip :nested_components2, :kino do
job :clock, ~e[@secondly]e do
date_time = DateTime.utc_now()
Fledex.Utils.PubSub.simple_broadcast(%{
clock_hour: date_time.hour,
clock_minute: date_time.minute,
clock_second: date_time.second
})
end
end
```
"""
defmacro job(name, pattern, do: block) do
# IO.puts("#{inspect name}, #{inspect pattern}, #{inspect block}")
defmacro job(name, pattern, options \\ [], do: block) do
ast_func = Dsl.ast_create_anonymous_func([], block)
# ast_func = {:fn, [], block}

quote do
Dsl.create_job(
unquote(name),
unquote(pattern),
unquote(options),
unquote(ast_func)
)
end
Expand Down
14 changes: 7 additions & 7 deletions lib/fledex/animation/animator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ defmodule Fledex.Animation.Animator do
Both of them can be set by defining an appropriate function and setting and resetting a reference at will
This module does not define any functions on its own, because the interface is defined
by `Fledex.Animation.Base`.
by `Fledex.Animation.AnimatorBase`.
"""
use Fledex.Animation.Base
use Fledex.Animation.AnimatorBase

require Logger

alias Fledex.Animation.Base
alias Fledex.Animation.AnimatorBase
alias Fledex.Leds
alias Fledex.LedStrip
alias Fledex.Utils.PubSub
Expand Down Expand Up @@ -82,8 +82,8 @@ defmodule Fledex.Animation.Animator do
state = %{
triggers: %{},
type: :animation,
def_func: &Base.default_def_func/1,
options: [send_config: &Base.default_send_config_func/1],
def_func: &AnimatorBase.default_def_func/1,
options: [send_config: &AnimatorBase.default_send_config_func/1],
effects: [],
strip_name: strip_name,
animation_name: animation_name
Expand Down Expand Up @@ -137,7 +137,7 @@ defmodule Fledex.Animation.Animator do
} = state
) do
# IO.puts("Update_Leds1: Key: #{Keyword.has_key?(options, :send_config_func)}")
send_config_func = options[:send_config] || (&Base.default_send_config_func/1)
send_config_func = options[:send_config] || (&AnimatorBase.default_send_config_func/1)
# IO.puts("Options: #{inspect options}")
# this is for compatibility reasons. if only a send_config_func is defined
# in the options list, then no options are defined. In that case we need to define
Expand Down Expand Up @@ -200,7 +200,7 @@ defmodule Fledex.Animation.Animator do
%{
type: config[:type] || state.type,
triggers: Map.merge(state.triggers, config[:triggers] || state[:triggers]),
def_func: Map.get(config, :def_func, &Base.default_def_func/1),
def_func: Map.get(config, :def_func, &AnimatorBase.default_def_func/1),
options: update_options(config[:options], config[:send_config_func]),
effects: update_effects(state.effects, config[:effects], state.strip_name),
# not to be updated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,50 @@
#
# SPDX-License-Identifier: Apache-2.0

defmodule Fledex.Animation.Base do
alias Fledex.Animation.Interface
defmodule Fledex.Animation.AnimatorBase do
alias Fledex.Animation.AnimatorInterface
alias Fledex.Leds

defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
use GenServer, opts
@behaviour Fledex.Animation.Interface
@behaviour Fledex.Animation.AnimatorInterface

alias Fledex.Animation.Base
alias Fledex.Animation.Interface
alias Fledex.Animation.AnimatorBase
alias Fledex.Animation.AnimatorInterface

# client side
# MARK: client side
@doc false
@spec start_link(config :: config_t, strip_name :: atom, animation_name :: atom) ::
GenServer.on_start()
def start_link(config, strip_name, animation_name) do
{:ok, _pid} =
GenServer.start_link(__MODULE__, {config, strip_name, animation_name},
name: Interface.build_name(strip_name, :animation, animation_name)
name: AnimatorInterface.build_name(strip_name, :animator, animation_name)
)
end

@doc false
@spec config(atom, atom, config_t) :: :ok
def config(strip_name, animation_name, config) do
GenServer.cast(
Interface.build_name(strip_name, :animation, animation_name),
AnimatorInterface.build_name(strip_name, :animator, animation_name),
{:config, config}
)
end

@doc false
@spec shutdown(atom, atom) :: :ok
def shutdown(strip_name, animation_name) do
GenServer.stop(Interface.build_name(strip_name, :animation, animation_name), :normal)
GenServer.stop(
AnimatorInterface.build_name(strip_name, :animator, animation_name),
:normal
)
end

defoverridable start_link: 3, config: 3, shutdown: 2

# server side
# MARK: server side
@impl GenServer
@spec handle_call(:info, {pid, any}, state_t) :: {:reply, {:ok, map}, state_t}
def handle_call(:info, _from, state) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
#
# SPDX-License-Identifier: Apache-2.0

defmodule Fledex.Animation.Interface do
defmodule Fledex.Animation.AnimatorInterface do
@moduledoc """
The behaviour for animations.
The behaviour for an Animator.
This behaviour is the interface expected by the `Fledex.Animation.Manager`
and should be implemented as a GenServer.
If you implement an animation you will have to implement those functions
but you can use `Fledex.Animation.Base` to assist you.
but you can use `Fledex.Animation.AnimatorBase` to assist you.
"""

@doc """
Expand Down Expand Up @@ -45,9 +45,9 @@ defmodule Fledex.Animation.Interface do
animators that have been removed. We do not keep a reference, but only a config
Therefore the animator needs to adhere to this naming convention to properly be shut down.
It is the responsibility of the Animator to set the servername correctly. The
`Fledex.Animation.Base` is doing this by default.
`Fledex.Animation.AnimatorBase` is doing this by default.
"""
@spec build_name(atom, :animation | :job | :coordinator, atom) :: atom
@spec build_name(atom, :animator | :job | :coordinator, atom) :: atom
def build_name(strip_name, type, animation_name)
when is_atom(strip_name) and is_atom(animation_name) do
Module.concat(Module.concat(strip_name, type), animation_name)
Expand Down
13 changes: 12 additions & 1 deletion lib/fledex/animation/job_scheduler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@
# SPDX-License-Identifier: Apache-2.0
# MARK: Quantum
defmodule Fledex.Animation.JobScheduler do
@callback start_link() ::
{:ok, pid}
| {:error, {:already_started, pid}}
| {:error, term}
@callback stop() :: :ok
@callback new_job() :: Quantum.Job.t()
@callback add_job(Quantum.Job.t() | {Crontab.CronExpression.t(), Job.task()}) ::
:ok
@callback run_job(atom) :: :ok
@callback delete_job(atom) :: :ok

use Quantum, otp_app: __MODULE__

@impl true
def config(opts \\ []) do
Quantum.scheduler_config(opts, __MODULE__, __MODULE__)
|> Keyword.put(:debug_logging, false)
|> Keyword.put(:debug_logging, false)
end
end

Expand Down
Loading

0 comments on commit 6626b53

Please sign in to comment.