From 64781e1a0c55fa4e60c50e4c9b412dc012a16fef Mon Sep 17 00:00:00 2001 From: SamJakob Date: Sat, 2 Sep 2023 22:56:02 +0100 Subject: [PATCH 1/5] v1.3.0 --- .formatter.exs | 4 + .github/workflows/lint_and_test.yml | 53 +++++++++ CHANGELOG.md | 24 +++++ README.md | 2 +- config/config.exs | 33 +----- config/dev.exs | 2 +- config/test.exs | 2 +- lib/task_after.ex | 26 +++-- lib/task_after/application.ex | 20 ++-- lib/task_after/worker.ex | 161 +++++++++++++++++----------- mix.exs | 21 ++-- test/task_after_test.exs | 93 +++++++++++++--- 12 files changed, 302 insertions(+), 139 deletions(-) create mode 100644 .formatter.exs create mode 100644 .github/workflows/lint_and_test.yml create mode 100644 CHANGELOG.md diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml new file mode 100644 index 0000000..58024d4 --- /dev/null +++ b/.github/workflows/lint_and_test.yml @@ -0,0 +1,53 @@ +name: Lint and Test + +on: + # Manual trigger + workflow_dispatch: + # Automated (push/pull request) + pull_request: + push: + branches: + - master + +jobs: + test: + runs-on: ubuntu-22.04 + env: + MIX_ENV: test + strategy: + fail-fast: false + matrix: + include: + - pair: + elixir: 1.15.5 + otp: 26.0.2 + lint: lint + - pair: + elixir: 1.5 + otp: 20 + - pair: + elixir: 1.5 + otp: 18 + steps: + - uses: actions/checkout@v2 + + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{matrix.pair.otp}} + elixir-version: ${{matrix.pair.elixir}} + + - name: Install Dependencies + run: mix deps.get --only test + + - run: mix format --check-formatted + if: ${{ matrix.lint }} + + - run: mix deps.get && mix deps.unlock --check-unused + if: ${{ matrix.lint }} + + - run: mix deps.compile + + - run: mix compile --warnings-as-errors + if: ${{ matrix.lint }} + + - run: mix test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..064fc4e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# v1.3.0 + + * Maintenance chores (update dependencies, deprecated methods., etc.) + * Update configuration to use `Config` module (instead of Mix) + * Change `Logger.warn` -> `Logger.warning` (former is deprecated) + * Bump minimum Elixir version to v1.5 (per the above) + * Fix documentation formatting typo + * Add CI testing with GitHub Actions + +# v1.2.0 + + * Add `TaskAfter.change_task_after/2` to allow changing a task. + +# v1.1.0 + + * Add `TaskAfter.cancel_task_after/2` to allow cancelling a task. + +# v1.0.0 + + * First published release + +# v0.1.0 + + * Initial release diff --git a/README.md b/README.md index 9dc9fe7..4451e80 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Install this package by adding `task_after` to your list of dependencies in `mix ```elixir def deps do [ - {:task_after, "~> 1.0.0"}, + {:task_after, "~> 1.3.0"}, ] end ``` diff --git a/config/config.exs b/config/config.exs index 0ccd839..4734f9f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,30 +1,7 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config +import Config -# This configuration is loaded before any dependency and is restricted -# to this project. If another project depends on this project, this -# file won't be loaded nor affect the parent project. For this reason, -# if you want to provide default values for your application for -# 3rd-party users, it should be done in your "mix.exs" file. +# Add any global configuration options here. -# You can configure for your application as: -# -# config :task_after, key: :value -# -# And access this configuration in your application as: -# -# Application.get_env(:task_after, :key) -# -# Or configure a 3rd-party app: -# -# config :logger, level: :info -# - -# It is also possible to import configuration files, relative to this -# directory. For example, you can emulate configuration per environment -# by uncommenting the line below and defining dev.exs, test.exs and such. -# Configuration from the imported file will override the ones defined -# here (which is why it is important to import them last). - -import_config "#{Mix.env}.exs" +# Finally, import env-specific configuration +# (to allow it to override the global defaults above). +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 3efdc13..16c7864 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :task_after, global_name: TaskAfter diff --git a/config/test.exs b/config/test.exs index 43bb990..ea364c7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :task_after, global_name: TaskAfter diff --git a/lib/task_after.ex b/lib/task_after.ex index 9067e63..695f5fb 100644 --- a/lib/task_after.ex +++ b/lib/task_after.ex @@ -37,14 +37,17 @@ defmodule TaskAfter do 42 """ - def task_after(timeout_after_ms, callback, opts \\ []) when is_integer(timeout_after_ms) and is_function(callback, 0) do - name = opts[:name] || opts[:pid] || Application.get_env(:task_after, :global_name, nil) || throw "TaskAfter: `:name` not defined and no global name defined" + def task_after(timeout_after_ms, callback, opts \\ []) + when is_integer(timeout_after_ms) and is_function(callback, 0) do + name = + opts[:name] || opts[:pid] || Application.get_env(:task_after, :global_name, nil) || + throw("TaskAfter: `:name` not defined and no global name defined") data = %{ timeout_after: timeout_after_ms, callback: callback, id: opts[:id], - send_result: opts[:send_result] || :async, + send_result: opts[:send_result] || :async } if opts[:no_return] do @@ -54,8 +57,6 @@ defmodule TaskAfter do end end - - @doc """ cancel_task_after @@ -80,11 +81,13 @@ defmodule TaskAfter do """ def cancel_task_after(task_id, opts \\ []) do - name = opts[:name] || opts[:pid] || Application.get_env(:task_after, :global_name, nil) || throw "TaskAfter: `:name` not defined and no global name defined" + name = + opts[:name] || opts[:pid] || Application.get_env(:task_after, :global_name, nil) || + throw("TaskAfter: `:name` not defined and no global name defined") data = %{ id: task_id, - send_result: opts[:run_result], + send_result: opts[:run_result] } if opts[:no_return] do @@ -94,7 +97,6 @@ defmodule TaskAfter do end end - @doc """ change_task_after @@ -102,7 +104,7 @@ defmodule TaskAfter do opts -> Can be: * `name: name` | `pid: pid` -> Specify a non-global task handler, if unspecified that the application `:global_name` must be specified - * `call_timeout: timeout` -> Override the timeout on calling to the TaskAfter.Worker` + * `call_timeout: timeout` -> Override the timeout on calling to the `TaskAfter.Worker` * `no_return: true` -> Do not return the id or error, just try to register and forget results otherwise * `callback: fun` -> Change the callback to this function * `timeout_after_ms: timeout` -> Change the timeout to this new value @@ -128,14 +130,16 @@ defmodule TaskAfter do :no_message """ def change_task_after(task_id, opts \\ []) do - name = opts[:name] || opts[:pid] || Application.get_env(:task_after, :global_name, nil) || throw "TaskAfter: `:name` not defined and no global name defined" + name = + opts[:name] || opts[:pid] || Application.get_env(:task_after, :global_name, nil) || + throw("TaskAfter: `:name` not defined and no global name defined") data = %{ id: task_id, callback: opts[:callback], timeout_after: opts[:timeout_after_ms], send_result: opts[:send_result], - recreate: opts[:recreate_if_necessary], + recreate: opts[:recreate_if_necessary] } if opts[:no_return] do diff --git a/lib/task_after/application.ex b/lib/task_after/application.ex index 9a3d651..9042a97 100644 --- a/lib/task_after/application.ex +++ b/lib/task_after/application.ex @@ -1,23 +1,23 @@ defmodule TaskAfter.Application do # See http://elixir-lang.org/docs/stable/elixir/Application.html # for more information on OTP Applications + @moduledoc false use Application def start(_type, _args) do - import Supervisor.Spec, warn: false - - worker = + children = case Application.get_env(:task_after, :global_name, nil) do - nil -> [] - name when is_atom(name) or is_tuple(name) -> [worker(TaskAfter.Worker, [[name: name]])] - end + nil -> + [] - children = - worker + name when is_atom(name) or is_tuple(name) -> + [ + {TaskAfter.Worker, [name: name]} + ] + end - opts = [strategy: :one_for_one, name: TaskAfter.Supervisor] - Supervisor.start_link(children, opts) + Supervisor.start_link(children, strategy: :one_for_one, name: TaskAfter.Supervisor) end end diff --git a/lib/task_after/worker.ex b/lib/task_after/worker.ex index 1738db5..ce15eb6 100644 --- a/lib/task_after/worker.ex +++ b/lib/task_after/worker.ex @@ -9,106 +9,102 @@ defmodule TaskAfter.Worker do # `[]` is the EVM/BEAM's `nil`, not the atom `nil`... defrecordp :t, [ - timeout_time: -1, # This must remain first + # This must remain first + timeout_time: -1, id: [], cb: [], - send_result: [], + send_result: [] ] - defrecordp :s, [ + defrecordp :s, next_id: 0, cbs_by_id: %{}, ids_by_time: :ordsets.new() - ] - def start_link(opts \\ []) do GenServer.start_link(__MODULE__, [], name: opts[:name]) end - def init(_opts) do {:ok, s()} end - def handle_cast({:register_callback, data}, state) do {state, result} = register_callback(data, state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: register_cast: #{inspect {timeout, result, state}}") + Logger.debug("TaskAfter: register_cast: #{inspect({timeout, result, state})}") {:noreply, state, timeout} end def handle_cast({:cancel_callback, data}, state) do {state, result} = cancel_callback(data, state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: cancel_cast: #{inspect {timeout, result, state}}") + Logger.debug("TaskAfter: cancel_cast: #{inspect({timeout, result, state})}") {:noreply, state, timeout} end def handle_cast({:change_callback, data}, state) do {state, result} = change_callback(data, state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: change_cast: #{inspect {timeout, result, state}}") + Logger.debug("TaskAfter: change_cast: #{inspect({timeout, result, state})}") {:noreply, state, timeout} end - def handle_call({:register_callback, data}, _from, state) do {state, result} = register_callback(data, state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: register_call: #{inspect {timeout, result, state}}") + Logger.debug("TaskAfter: register_call: #{inspect({timeout, result, state})}") {:reply, result, state, timeout} end def handle_call({:cancel_callback, data}, _from, state) do {state, result} = cancel_callback(data, state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: cancel_call: #{inspect {timeout, result, state}}") + Logger.debug("TaskAfter: cancel_call: #{inspect({timeout, result, state})}") {:reply, result, state, timeout} end def handle_call({:change_callback, data}, _from, state) do {state, result} = change_callback(data, state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: change_call: #{inspect {timeout, result, state}}") + Logger.debug("TaskAfter: change_call: #{inspect({timeout, result, state})}") {:reply, result, state, timeout} end - def handle_info(:timeout, state) do state = process(state) {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: timeout: #{inspect {timeout, state}}") + Logger.debug("TaskAfter: timeout: #{inspect({timeout, state})}") {:noreply, state, timeout} end def handle_info({ref, res}, state) when is_reference(ref) do {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: info task msg: #{inspect {timeout, res, state}}") + Logger.debug("TaskAfter: info task msg: #{inspect({timeout, res, state})}") {:noreply, state, timeout} end - def handle_info({:DOWN, ref, :process, pid, :normal}, state) when is_reference(ref) and is_pid(pid) do + def handle_info({:DOWN, ref, :process, pid, :normal}, state) + when is_reference(ref) and is_pid(pid) do {state, timeout} = get_next_timeout(state) - Logger.debug("TaskAfter: info task down: #{inspect {timeout, state}}") + Logger.debug("TaskAfter: info task down: #{inspect({timeout, state})}") {:noreply, state, timeout} end def handle_info(msg, state) do - Logger.warn("TaskAfter: Unknown message received: #{inspect msg}") + Logger.warning("TaskAfter: Unknown message received: #{inspect(msg)}") {state, timeout} = get_next_timeout(state) {:noreply, state, timeout} end - - - - defp register_callback(data, state) do case data[:id] do - nil -> install_callback(data, state) - [] -> install_callback(data, state) + nil -> + install_callback(data, state) + + [] -> + install_callback(data, state) + id -> case s(state, :cbs_by_id)[id] do nil -> install_callback(data, state) @@ -127,21 +123,28 @@ defmodule TaskAfter.Worker do end cond do - not is_integer(data[:timeout_time]) and not is_integer(data[:timeout_after]) -> {state, {:error, {:invalid_argument, :timeout}}} - not is_function(data.callback, 0) -> {state, {:error, {:invalid_argument, :callback}}} - true -> install_callback(data, id, state) + not is_integer(data[:timeout_time]) and not is_integer(data[:timeout_after]) -> + {state, {:error, {:invalid_argument, :timeout}}} + + not is_function(data.callback, 0) -> + {state, {:error, {:invalid_argument, :callback}}} + + true -> + install_callback(data, id, state) end end defp install_callback(data, id, s(cbs_by_id: cbs, ids_by_time: times) = state) do timeout_time = case data[:timeout_time] do - nil -> ((data.timeout_after) + (get_current_ms())) - [] -> ((data.timeout_after) + (get_current_ms())) + nil -> data.timeout_after + get_current_ms() + [] -> data.timeout_after + get_current_ms() time -> time end - task = t(timeout_time: timeout_time, id: id, cb: data.callback, send_result: data[:send_result]) + task = + t(timeout_time: timeout_time, id: id, cb: data.callback, send_result: data[:send_result]) + # Putting the task into both since putting it only in one but with a mapping struct in the other would actually eat # 'more' memory, so no point... cbs = Map.put(cbs, id, task) @@ -152,11 +155,17 @@ defmodule TaskAfter.Worker do {state, result} end - - defp cancel_callback(%{id: id, send_result: send_result}, s(cbs_by_id: cbs, ids_by_time: times) = state) do + defp cancel_callback( + %{id: id, send_result: send_result}, + s(cbs_by_id: cbs, ids_by_time: times) = state + ) do case Map.get(cbs, id) do - nil -> {state, {:error, {:does_not_exist, id}}} - [] -> {state, {:error, {:does_not_exist, id}}} + nil -> + {state, {:error, {:does_not_exist, id}}} + + [] -> + {state, {:error, {:does_not_exist, id}}} + task -> result = run_task(send_result, task) cbs = Map.delete(cbs, id) @@ -166,115 +175,143 @@ defmodule TaskAfter.Worker do end end - - defp change_callback(%{id: id, callback: callback, timeout_after: timeout_after, send_result: send_result, recreate: recreate}, s(cbs_by_id: cbs, ids_by_time: times) = state) do + defp change_callback( + %{ + id: id, + callback: callback, + timeout_after: timeout_after, + send_result: send_result, + recreate: recreate + }, + s(cbs_by_id: cbs, ids_by_time: times) = state + ) do case List.wrap(Map.get(cbs, id)) do - [] when recreate in [nil, false] -> {state, {:error, {:does_not_exist, id}}} + [] when recreate in [nil, false] -> + {state, {:error, {:does_not_exist, id}}} + [] when recreate == true -> callback = get_default_value(callback) timeout_after = get_default_value(timeout_after) send_result = get_default_value(send_result) + data = %{ timeout_after: timeout_after, id: id, callback: callback, - send_result: send_result, + send_result: send_result } + install_callback(data, state) + [t(timeout_time: timeout_time, id: ^id, cb: cb, send_result: old_send_result) = task] -> callback = get_non_default_value(callback) timeout_after = get_non_default_value(timeout_after) send_result = get_non_default_value(send_result) cbs = Map.delete(cbs, id) times = List.delete(times, task) + data = %{ id: id, callback: callback || cb, - send_result: send_result || old_send_result, + send_result: send_result || old_send_result } - data = if timeout_after do - Map.put_new(data, :timeout_after, timeout_after) - else - Map.put_new(data, :timeout_time, timeout_time) - end + + data = + if timeout_after do + Map.put_new(data, :timeout_after, timeout_after) + else + Map.put_new(data, :timeout_time, timeout_time) + end + state = s(state, cbs_by_id: cbs, ids_by_time: times) install_callback(data, state) end end - defp get_non_default_value({:default, _value}), do: nil defp get_non_default_value(value), do: value defp get_default_value({:default, value}), do: value defp get_default_value(value), do: value - defp get_next_timeout(s(ids_by_time: [t(timeout_time: next) | _]) = state) do case next - get_current_ms() do timeout when timeout < 0 -> {state, 0} timeout -> {state, timeout} end end - defp get_next_timeout(state), do: {state, :infinity} + defp get_next_timeout(state), do: {state, :infinity} defp get_current_ms() do :erlang.monotonic_time(:millisecond) end - defp generate_new_id(s(next_id: next_id) = state) do - state = s(state, next_id: next_id+1) + state = s(state, next_id: next_id + 1) id = {__MODULE__, next_id} {state, id} end - - defp process(s(ids_by_time: [t(timeout_time: timeout_time, id: id, cb: _cb, send_result: send_result)=task | rest], cbs_by_id: cbs) = state) do + defp process( + s( + ids_by_time: [ + t(timeout_time: timeout_time, id: id, cb: _cb, send_result: send_result) = task + | rest + ], + cbs_by_id: cbs + ) = state + ) do cur = get_current_ms() + if timeout_time > cur do - state # No more to process since they are later + # No more to process since they are later + state else _ = run_task(send_result, task) state = s(state, ids_by_time: rest, cbs_by_id: Map.delete(cbs, id)) process(state) end end + defp process(state) do - state # No more to process since it is empty + # No more to process since it is empty + state end - defp run_task(send_result, task) defp run_task(nil, t(cb: cb)), do: cb defp run_task([], t(cb: cb)), do: cb + defp run_task(:async, t(id: id, cb: cb)) do Task.async(fn -> safe_call_cb(cb, id) end) :task end + defp run_task(:in_process, t(id: id, cb: cb)) do - safe_call_cb(cb, id) # Uhh, hope they know what they are doing... + # Uhh, hope they know what they are doing... + safe_call_cb(cb, id) end + defp run_task(pid, t(id: id, cb: cb)) when is_pid(pid) do Task.async(fn -> send(pid, safe_call_cb(cb, id)) end) :task end - defp safe_call_cb(cb, id) do try do cb.() rescue exc -> - Logger.warn("TaskAfter: Task `#{inspect id}` crashed due to exception: #{Exception.message(exc)}") + Logger.warning( + "TaskAfter: Task `#{inspect(id)}` crashed due to exception: #{Exception.message(exc)}" + ) + {:error, exc} catch error -> - Logger.warn("TaskAfter: Task `#{inspect id}` crashed due to: #{inspect error}") + Logger.warning("TaskAfter: Task `#{inspect(id)}` crashed due to: #{inspect(error)}") {:error, error} end end - - end diff --git a/mix.exs b/mix.exs index 1422244..014792f 100644 --- a/mix.exs +++ b/mix.exs @@ -4,14 +4,14 @@ defmodule TaskAfter.Mixfile do def project do [ app: :task_after, - version: "1.2.0", - elixir: "~> 1.4", + version: "1.3.0", + elixir: "~> 1.5", description: description(), package: package(), docs: docs(), - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - deps: deps(), + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + deps: deps() ] end @@ -27,29 +27,28 @@ defmodule TaskAfter.Mixfile do name: :task_after, maintainers: ["OvermindDL1"], links: %{ - "Github" => "https://github.com/OvermindDL1/task_after", - }, + "Github" => "https://github.com/OvermindDL1/task_after" + } ] end def docs do [ - #logo: "path/to/logo.png", extras: ["README.md"], - main: "readme", + main: "readme" ] end def application do [ extra_applications: [:logger], - mod: {TaskAfter.Application, []}, + mod: {TaskAfter.Application, []} ] end defp deps do [ - {:ex_doc, "~> 0.21.1", only: [:dev]}, + {:ex_doc, "~> 0.30.6", only: [:dev]} ] end end diff --git a/test/task_after_test.exs b/test/task_after_test.exs index ff548a3..349c768 100644 --- a/test/task_after_test.exs +++ b/test/task_after_test.exs @@ -14,13 +14,19 @@ defmodule TaskAfterTest do end test "TaskAfter with custom id" do - assert {:ok, :my_id} = TaskAfter.task_after(500, fn -> 42 end, id: :my_id, send_result: self()) + assert {:ok, :my_id} = + TaskAfter.task_after(500, fn -> 42 end, id: :my_id, send_result: self()) + assert_receive(42, 600) end test "TaskAfter with custom id duplicate fails" do - assert {:ok, :dup_id} = TaskAfter.task_after(500, fn -> 42 end, id: :dup_id, send_result: self()) - assert {:error, {:duplicate_id, :dup_id}} = TaskAfter.task_after(500, fn -> 42 end, id: :dup_id, send_result: self()) + assert {:ok, :dup_id} = + TaskAfter.task_after(500, fn -> 42 end, id: :dup_id, send_result: self()) + + assert {:error, {:duplicate_id, :dup_id}} = + TaskAfter.task_after(500, fn -> 42 end, id: :dup_id, send_result: self()) + assert_receive(42, 600) end @@ -41,14 +47,20 @@ defmodule TaskAfterTest do test "TaskAfter non-global by name" do assert {:ok, pid} = TaskAfter.Worker.start_link(name: :testing_name) - {:ok, _auto_id} = TaskAfter.task_after(500, fn -> 42 end, send_result: self(), name: :testing_name) + + {:ok, _auto_id} = + TaskAfter.task_after(500, fn -> 42 end, send_result: self(), name: :testing_name) + assert_receive(42, 600) GenServer.stop(pid) end test "TaskAfter non-global by pid" do assert {:ok, pid} = TaskAfter.Worker.start_link() - assert {:ok, _auto_id} = TaskAfter.task_after(500, fn -> 42 end, send_result: self(), pid: pid) + + assert {:ok, _auto_id} = + TaskAfter.task_after(500, fn -> 42 end, send_result: self(), pid: pid) + assert_receive(42, 600) GenServer.stop(pid) end @@ -56,7 +68,13 @@ defmodule TaskAfterTest do test "TaskAfter in process (unsafe, can freeze the task worker if the task does not return fast)" do assert {:ok, pid} = TaskAfter.Worker.start_link() s = self() - assert {:ok, _auto_id} = TaskAfter.task_after(500, fn -> send(s, self()) end, send_result: :in_process, pid: pid) + + assert {:ok, _auto_id} = + TaskAfter.task_after(500, fn -> send(s, self()) end, + send_result: :in_process, + pid: pid + ) + assert_receive(^pid, 600) GenServer.stop(pid) end @@ -98,40 +116,87 @@ defmodule TaskAfterTest do len = &length/1 d = len.([]) assert {:ok, _auto_id0} = TaskAfter.task_after(100, fn -> send(s, 21) end) - assert {:ok, _auto_id1} = TaskAfter.task_after(250, fn -> send(s, 1/d) end) + assert {:ok, _auto_id1} = TaskAfter.task_after(250, fn -> send(s, 1 / d) end) assert {:ok, _auto_id2} = TaskAfter.task_after(500, fn -> send(s, 42) end) assert_receive(42, 600) assert_receive(21, 1) - assert :no_message = (receive do m -> m after 1 -> :no_message end) + + assert :no_message = + (receive do + m -> m + after + 1 -> :no_message + end) end test "TaskAfter and replace callback without recreate" do assert {:ok, auto_id} = TaskAfter.task_after(500, fn -> 1 end, send_result: self()) assert {:ok, ^auto_id} = TaskAfter.change_task_after(auto_id, callback: fn -> 2 end) assert_receive(2, 600) - assert {:error, {:does_not_exist, ^auto_id}} = TaskAfter.change_task_after(auto_id, callback: fn -> 3 end) + + assert {:error, {:does_not_exist, ^auto_id}} = + TaskAfter.change_task_after(auto_id, callback: fn -> 3 end) end test "TaskAfter and replace callback and timeout with recreate" do assert {:ok, auto_id} = TaskAfter.task_after(500, fn -> 1 end, send_result: self()) - assert {:ok, ^auto_id} = TaskAfter.change_task_after(auto_id, recreate_if_necessary: true, timeout_after_ms: 500, send_result: self(), callback: fn -> 2 end) + + assert {:ok, ^auto_id} = + TaskAfter.change_task_after(auto_id, + recreate_if_necessary: true, + timeout_after_ms: 500, + send_result: self(), + callback: fn -> 2 end + ) + assert_receive(2, 600) - assert {:ok, ^auto_id} = TaskAfter.change_task_after(auto_id, recreate_if_necessary: true, timeout_after_ms: 500, send_result: self(), callback: fn -> 3 end) + + assert {:ok, ^auto_id} = + TaskAfter.change_task_after(auto_id, + recreate_if_necessary: true, + timeout_after_ms: 500, + send_result: self(), + callback: fn -> 3 end + ) + assert_receive(3, 600) end test "TaskAfter and replace callback without timeout with recreate" do assert {:ok, auto_id} = TaskAfter.task_after(500, fn -> 1 end, send_result: self()) - assert {:ok, ^auto_id} = TaskAfter.change_task_after(auto_id, recreate_if_necessary: true, timeout_after_ms: 500, send_result: self(), callback: fn -> 2 end) + + assert {:ok, ^auto_id} = + TaskAfter.change_task_after(auto_id, + recreate_if_necessary: true, + timeout_after_ms: 500, + send_result: self(), + callback: fn -> 2 end + ) + assert_receive(2, 600) - assert {:ok, ^auto_id} = TaskAfter.change_task_after(auto_id, recreate_if_necessary: true, timeout_after_ms: {:default, 500}, send_result: self(), callback: fn -> 3 end) + + assert {:ok, ^auto_id} = + TaskAfter.change_task_after(auto_id, + recreate_if_necessary: true, + timeout_after_ms: {:default, 500}, + send_result: self(), + callback: fn -> 3 end + ) + assert_receive(3, 600) end test "TaskAfter and replace timeout without recreate" do assert {:ok, auto_id} = TaskAfter.task_after(200, fn -> 1 end, send_result: self()) assert {:ok, ^auto_id} = TaskAfter.change_task_after(auto_id, timeout_after_ms: 500) - assert :no_message = (receive do m -> m after 300 -> :no_message end) + + assert :no_message = + (receive do + m -> m + after + 300 -> :no_message + end) + assert_receive(1, 600) end end From 0e7710f54643bb5ac862466fac3bccffb2e1e58f Mon Sep 17 00:00:00 2001 From: SamJakob Date: Sat, 2 Sep 2023 22:58:10 +0100 Subject: [PATCH 2/5] Update CI matrix and mix.lock --- .github/workflows/lint_and_test.yml | 7 +++++-- mix.lock | 11 ++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index 58024d4..480adc9 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -24,10 +24,13 @@ jobs: lint: lint - pair: elixir: 1.5 - otp: 20 + otp: 20.3.8.26 - pair: elixir: 1.5 - otp: 18 + otp: 20.0 + - pair: + elixir: 1.5 + otp: 18.3.4.11 steps: - uses: actions/checkout@v2 diff --git a/mix.lock b/mix.lock index 3622ed8..e789ac2 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,8 @@ %{ - "earmark": {:hex, :earmark, "1.3.5", "0db71c8290b5bc81cb0101a2a507a76dca659513984d683119ee722828b424f6", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.21.1", "5ac36660846967cd869255f4426467a11672fec3d8db602c429425ce5b613b90", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, } From 4f4c6653496ef97b1e408897feb3fc0a83b88843 Mon Sep 17 00:00:00 2001 From: SamJakob Date: Sat, 2 Sep 2023 23:06:20 +0100 Subject: [PATCH 3/5] Put runner on ubuntu20.04 to test older Elixir versions --- .github/workflows/lint_and_test.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index 480adc9..1e3f390 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -11,7 +11,7 @@ on: jobs: test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 env: MIX_ENV: test strategy: @@ -22,15 +22,17 @@ jobs: elixir: 1.15.5 otp: 26.0.2 lint: lint + # Deprecated versions (consider removing and upgrading to Ubuntu 22.04) - pair: elixir: 1.5 otp: 20.3.8.26 - pair: elixir: 1.5 otp: 20.0 - - pair: - elixir: 1.5 - otp: 18.3.4.11 + # Only built for Ubuntu 18.04 which has been deprecated as a GitHub Actions runner. +# - pair: +# elixir: 1.5 +# otp: 18.3.4.11 steps: - uses: actions/checkout@v2 From 23a6ee46e75f8b41196c644e5454243035bb836a Mon Sep 17 00:00:00 2001 From: SamJakob Date: Sat, 2 Sep 2023 23:10:50 +0100 Subject: [PATCH 4/5] Bump minimum Elixir version to 1.9 and minimum OTP to 20 --- .github/workflows/lint_and_test.yml | 12 ++++-------- mix.exs | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index 1e3f390..7c61f66 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -24,15 +24,11 @@ jobs: lint: lint # Deprecated versions (consider removing and upgrading to Ubuntu 22.04) - pair: - elixir: 1.5 - otp: 20.3.8.26 + elixir: 1.9 + otp: 22 - pair: - elixir: 1.5 - otp: 20.0 - # Only built for Ubuntu 18.04 which has been deprecated as a GitHub Actions runner. -# - pair: -# elixir: 1.5 -# otp: 18.3.4.11 + elixir: 1.9 + otp: 20 steps: - uses: actions/checkout@v2 diff --git a/mix.exs b/mix.exs index 014792f..e276897 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule TaskAfter.Mixfile do [ app: :task_after, version: "1.3.0", - elixir: "~> 1.5", + elixir: "~> 1.9", description: description(), package: package(), docs: docs(), From 8b4240e6ff0dacf683d44be833493f3a02cb7587 Mon Sep 17 00:00:00 2001 From: SamJakob Date: Sat, 2 Sep 2023 23:13:34 +0100 Subject: [PATCH 5/5] Bump minimum elixir version to 1.11 for config_env/0 --- .github/workflows/lint_and_test.yml | 8 ++++---- CHANGELOG.md | 2 +- mix.exs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index 7c61f66..0ef2da7 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -24,11 +24,11 @@ jobs: lint: lint # Deprecated versions (consider removing and upgrading to Ubuntu 22.04) - pair: - elixir: 1.9 - otp: 22 + elixir: 1.11 + otp: 24 - pair: - elixir: 1.9 - otp: 20 + elixir: 1.11 + otp: 21 steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 064fc4e..b3d53c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ * Maintenance chores (update dependencies, deprecated methods., etc.) * Update configuration to use `Config` module (instead of Mix) * Change `Logger.warn` -> `Logger.warning` (former is deprecated) - * Bump minimum Elixir version to v1.5 (per the above) + * Bump minimum Elixir version to v1.11 (per the above) * Fix documentation formatting typo * Add CI testing with GitHub Actions diff --git a/mix.exs b/mix.exs index e276897..b5c4528 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule TaskAfter.Mixfile do [ app: :task_after, version: "1.3.0", - elixir: "~> 1.9", + elixir: "~> 1.11", description: description(), package: package(), docs: docs(),