VBT.FixedJob (vbt v0.1.0) View Source

Helper for running jobs with fixed schedules (e.g. once a day at midnight).

Basic usage

In most cases it's advised to define a dedicated module. For example, the following module defines a cleanup job which runs once a day at midnight UTC:

defmodule DailyCleanup do
  def child_spec(_) do
    VBT.FixedJob.child_spec(
      id: __MODULE__,
      name: __MODULE__,
      run: &cleanup/0,

      # configures desired time
      when: %{hour: 0, minute: 0},

      # prevents periodic job from running automatically in test mode
      mode: unquote(if Mix.env() == :test, do: :manual, else: :auto)
    )
  end

  defp cleanup() do
    # ...
  end
end

Now, you can include DailyCleanup as a child of some supervisor.

Testing

The job can be tested as follows:

test "cleanup scheduler" do
  # The job is registered under a name so we can find its pid
  scheduler_pid = Process.whereis(DailyCleanup)

  # Needed only if the scheduler works with the database
  Sandbox.allow(Repo, self(), scheduler_pid)

  # mock the current time in the scheduler
  VBT.FixedJob.set_time(scheduler_pid, %{hour: 0, minute: 0})

  # ticks the scheduler, waits for the job to finish, and asserts that it exited normally
  assert Periodic.Test.sync_tick(scheduler_pid) == {:ok, :normal}

  # verify side-effects of the job here
end

You can test multiple different jobs from separate async ExUnit cases. However, the same job should either be tested from a single case (preferred), or all cases testing the scheduler should be synchronous (async: false).

Options

  • :when - parts of the DateTime.t struct which you want to match. In addition, the :day_of_week key is supported with values of the day_of_week/0 type.
  • :now_fun - Optional zero arity function (or MFA) which is invoked by the scheduler to get the current date/time. By default, DateTime.utc_now/0 is used.
  • The remaining options in the opts/0 type are specific to Periodic. See the corresponding docs for details.

Link to this section Summary

Functions

Returns the supervisor child specification for the scheduler process.

Sets the time of the scheduler process.

Starts the scheduler process.

Link to this section Types

Specs

day_of_week() ::
  :monday | :tuesday | :wednesday | :thursday | :friday | :saturday | :sunday

Specs

filter() :: %{
  optional(:minute) => Calendar.minute(),
  optional(:hour) => Calendar.hour(),
  optional(:day_of_week) => day_of_week(),
  optional(:day) => Calendar.day(),
  optional(:month) => Calendar.month(),
  optional(:year) => Calendar.year()
}

Specs

opts() :: [
  id: any(),
  name: GenServer.name(),
  telemetry_id: term(),
  run: (() -> any()) | {module(), atom(), [any()]},
  when: filter(),
  now_fun: (() -> any()) | {module(), atom(), [any()]},
  on_overlap: :run | :ignore | :stop_previous,
  timeout: pos_integer() | :infinity,
  job_shutdown: :brutal_kill | :infinity | non_neg_integer(),
  mode: :auto | :manual
]

Link to this section Functions

Specs

child_spec(opts()) :: Supervisor.child_spec()

Returns the supervisor child specification for the scheduler process.

Link to this function

set_time(scheduler, time_overrides)

View Source

Specs

set_time(GenServer.server(), filter()) :: :ok

Sets the time of the scheduler process.

This function should only be used in tests, and will only work if the scheduler mode is set to :manual. Typically you want to invoke this function before calling Periodic.Test.tick/2. You don't need to provide the complete date/time. Only the parts which are of interest can be passed. The scheduler will fill in the rest via the now function (see the :now_fun option).

Specs

start_link(opts()) :: GenServer.on_start()

Starts the scheduler process.