| defmodule Couch.Test.Setup do | |
| @moduledoc """ | |
| Allows to chain setup functions. | |
| Example of using: | |
| ``` | |
| alias Couch,Test.Utils | |
| def with_db_name(context, setup) do | |
| setup = | |
| setup | |
| |> Step.Start.new(:start, extra_apps: [:chttpd]) | |
| |> Step.User.new(:admin, roles: [:server_admin]) | |
| |> Setup.run() | |
| context = | |
| Map.merge(context, %{ | |
| db_name: Utils.random_name("db") | |
| base_url: setup |> Setup.get(:start) |> Step.Start.clustered_url(), | |
| user: setup |> Setup.get(:admin) |> Step.User.name() | |
| }) | |
| {context, setup} | |
| end | |
| @tag setup: &__MODULE__.with_db_name/2 | |
| test "Create", %{db_name: db_name, user: user} do | |
| ... | |
| end | |
| ``` | |
| """ | |
| import ExUnit.Callbacks, only: [on_exit: 1] | |
| import ExUnit.Assertions, only: [assert: 2] | |
| require Logger | |
| alias Couch.Test.Setup | |
| alias Couch.Test.Setup.Step | |
| defstruct stages: [], by_type: %{}, state: %{} | |
| def step(%Setup{stages: stages} = setup, id, step) do | |
| %{setup | stages: [{id, step} | stages]} | |
| end | |
| defp setup_step({id, step}, %Setup{state: state, by_type: by_type} = setup) do | |
| %module{} = step | |
| # credo:disable-for-next-line Credo.Check.Warning.LazyLogging | |
| Logger.debug("Calling 'setup/2' for '#{module}'") | |
| step = module.setup(setup, step) | |
| state = Map.put(state, id, step) | |
| by_type = Map.update(by_type, module, [id], fn ids -> [id | ids] end) | |
| on_exit(fn -> | |
| # credo:disable-for-next-line Credo.Check.Warning.LazyLogging | |
| Logger.debug("Calling 'teardown/3' for '#{module}'") | |
| try do | |
| module.teardown(setup, step) | |
| :ok | |
| catch | |
| _ -> :ok | |
| _, _ -> :ok | |
| end | |
| end) | |
| {{id, step}, %{setup | state: state, by_type: by_type}} | |
| end | |
| def run(%Setup{stages: stages} = setup) do | |
| {stages, setup} = stages | |
| |> Enum.reverse | |
| |> Enum.map_reduce(setup, &setup_step/2) | |
| %{setup | stages: stages} | |
| end | |
| def setup(ctx) do | |
| Map.get(ctx, :__setup) | |
| end | |
| def setup(ctx, setup_fun) do | |
| setup = %Setup{} |> Step.Config.new(:test_config, config_file: nil) | |
| {ctx, setup} = setup_fun.(ctx, setup) | |
| assert not Map.has_key?(ctx, :__setup), "Key `__setup` is reserved for internal purposes" | |
| Map.put(ctx, :__setup, setup) | |
| end | |
| def completed?(%Setup{by_type: by_type}, step) do | |
| Map.has_key?(by_type, step) | |
| end | |
| def all_for(%Setup{by_type: by_type, state: state}, step_module) do | |
| Map.take(state, by_type[step_module] || []) | |
| end | |
| def reduce_for(setup, step_module, acc, fun) do | |
| Enum.reduce(all_for(setup, step_module), acc, fun) | |
| end | |
| def get(%Setup{state: state}, id) do | |
| state[id] | |
| end | |
| end |