Document that ExUnit.Case :async defaults to false

defmodule ExUnit.Case do
  @moduledoc """
  Sets up an ExUnit test case.

  This module must be used in other modules as a way to configure
  and prepare them for testing.

  When used, it accepts the following options:

    * :async - configure Elixir to run that specific test case in parallel with
      others. Must be used for performance when your test cases do not change
      any global state. It defaults to `false`.

  This module automatically includes all callbacks defined in
  `ExUnit.Callbacks`. See that module's documentation for more
  information.

  ## Examples

       defmodule AssertionTest do
         # Use the module
         use ExUnit.Case, async: true

         # The `test` macro is imported by ExUnit.Case
         test "always pass" do
           assert true
         end
       end

  ## Context

  All tests receive a context as an argument. The context is particularly
  useful for sharing information between callbacks and tests:

      defmodule KVTest do
        use ExUnit.Case

        setup do
          {:ok, pid} = KV.start_link
          {:ok, [pid: pid]}
        end

        test "stores key-values", context do
          assert KV.put(context[:pid], :hello, :world) == :ok
          assert KV.get(context[:pid], :hello) == :world
        end
      end

  As the context is a map, it can be pattern matched on to extract
  information:

      test "stores key-values", %{pid: pid} do
        assert KV.put(pid, :hello, :world) == :ok
        assert KV.get(pid, :hello) == :world
      end

  ## Tags

  The context is used to pass information from the callbacks to
  the test. In order to pass information from the test to the
  callback, ExUnit provides tags.

  By tagging a test, the tag value can be accessed in the context,
  allowing the developer to customize the test. Let's see an
  example:

      defmodule FileTest do
        # Changing directory cannot be async
        use ExUnit.Case, async: false

        setup context do
          # Read the :cd tag value
          if cd = context[:cd] do
            prev_cd = File.cwd!
            File.cd!(cd)
            on_exit fn -> File.cd!(prev_cd) end
          end

          :ok
        end

        @tag cd: "fixtures"
        test "reads utf-8 fixtures" do
          File.read("hello")
        end
      end

  In the example above, we have defined a tag called `:cd` that is
  read in the setup callback to configure the working directory the
  test is going to run on.

  Tags are also very effective when used with case templates
  (`ExUnit.CaseTemplate`) allowing callbacks in the case template
  to customize the test behaviour.

  Note a tag can be set in two different ways:

      @tag key: value
      @tag :key       # equivalent to setting @tag key: true

  If a tag is given more than once, the last value wins.

  ### Module tags

  A tag can be set for all tests in a module by setting `@moduletag`:

      @moduletag :external

  If the same key is set via `@tag`, the `@tag` value has higher
  precedence.

  ### Known tags

  The following tags are set automatically by ExUnit and are
  therefore reserved:

    * `:case` - the test case module
    * `:test` - the test name
    * `:line` - the line on which the test was defined
    * `:file` - the file on which the test was defined

  The following tags customize how tests behaves:

    * `:timeout` - customizes the test timeout in milliseconds (defaults to 30000)

  ## Filters

  Tags can also be used to identify specific tests, which can then
  be included or excluded using filters. The most common functionality
  is to exclude some particular tests from running, which can be done
  via `ExUnit.configure/1`:

      # Exclude all external tests from running
      ExUnit.configure(exclude: [external: true])

  From now on, ExUnit will not run any test that has the `external` flag
  set to true. This behaviour can be reversed with the `:include` option
  which is usually passed through the command line:

      mix test --include external:true

  Run `mix help test` for more information on how to run filters via Mix.

  Another use case for tags and filters is to exclude all tests that have
  a particular tag by default, regardless of its value, and include only
  a certain subset:

      ExUnit.configure(exclude: :os, include: [os: :unix])

  Keep in mind that all tests are included by default, so unless they are
  excluded first, the `include` option has no effect.
  """

  @doc false
  defmacro __using__(opts) do
    async = Keyword.get(opts, :async, false)

    unless Process.whereis(ExUnit.Server) do
      raise "cannot use ExUnit.Case without starting the ExUnit application, " <>
            "please call ExUnit.start() or explicitly start the :ex_unit app"
    end

    quote do
      unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
        if unquote(async) do
          ExUnit.Server.add_async_case(__MODULE__)
        else
          ExUnit.Server.add_sync_case(__MODULE__)
        end

        Enum.each [:ex_unit_tests, :tag, :moduletag],
          &Module.register_attribute(__MODULE__, &1, accumulate: true)

        @before_compile ExUnit.Case
        @ex_unit_test_names HashSet.new
        use ExUnit.Callbacks
      end

      import ExUnit.Callbacks
      import ExUnit.Assertions
      import ExUnit.Case
      import ExUnit.DocTest
    end
  end

  @doc """
  Define a test with a string.

  Provides a convenient macro that allows a test to be
  defined with a string. This macro automatically inserts
  the atom `:ok` as the last line of the test. That said,
  a passing test always returns `:ok`, but, more importantly,
  it forces Elixir to not tail call optimize the test and
  therefore avoids hiding lines from the backtrace.

  ## Examples

      test "true is equal to true" do
        assert true == true
      end

  """
  defmacro test(message, var \\ quote(do: _), contents) do
    contents =
      case contents do
        [do: block] ->
          quote do
            unquote(block)
            :ok
          end
        _ ->
          quote do
            try(unquote(contents))
            :ok
          end
      end

    var      = Macro.escape(var)
    contents = Macro.escape(contents, unquote: true)

    quote bind_quoted: binding do
      test = :"test #{message}"
      ExUnit.Case.__on_definition__(__ENV__, test)
      def unquote(test)(unquote(var)), do: unquote(contents)
    end
  end

  @doc false
  defmacro __before_compile__(_) do
    quote do
      def __ex_unit__(:case) do
        %ExUnit.TestCase{name: __MODULE__, tests: @ex_unit_tests}
      end
    end
  end

  @doc false
  def __on_definition__(env, name) do
    mod  = env.module
    tags = Module.get_attribute(mod, :tag) ++ Module.get_attribute(mod, :moduletag)
    tags = tags |> normalize_tags |> Map.merge(%{line: env.line, file: env.file})

    test = %ExUnit.Test{name: name, case: mod, tags: tags}
    test_names = Module.get_attribute(mod, :ex_unit_test_names)

    unless name in test_names do
      Module.put_attribute(mod, :ex_unit_tests, test)
      Module.put_attribute(mod, :ex_unit_test_names, HashSet.put(test_names, name))
    end

    Module.delete_attribute(mod, :tag)
  end

  defp normalize_tags(tags) do
    Enum.reduce Enum.reverse(tags), %{}, fn
      tag, acc when is_atom(tag) -> Map.put(acc, tag, true)
      tag, acc when is_list(tag) -> Dict.merge(acc, tag)
    end
  end
end

defmodule ExUnit.Case do
  @moduledoc """
  Sets up an ExUnit test case.

  This module must be used in other modules as a way to configure
  and prepare them for testing.

  When used, it accepts the following options:

    * :async - configure Elixir to run that specific test case in parallel with
      others. Must be used for performance when your test cases do not change
      any global state.

  This module automatically includes all callbacks defined in
  `ExUnit.Callbacks`. See that module's documentation for more
  information.

  ## Examples

       defmodule AssertionTest do
         # Use the module
         use ExUnit.Case, async: true

         # The `test` macro is imported by ExUnit.Case
         test "always pass" do
           assert true
         end
       end

  ## Context

  All tests receive a context as an argument. The context is particularly
  useful for sharing information between callbacks and tests:

      defmodule KVTest do
        use ExUnit.Case

        setup do
          {:ok, pid} = KV.start_link
          {:ok, [pid: pid]}
        end

        test "stores key-values", context do
          assert KV.put(context[:pid], :hello, :world) == :ok
          assert KV.get(context[:pid], :hello) == :world
        end
      end

  As the context is a map, it can be pattern matched on to extract
  information:

      test "stores key-values", %{pid: pid} do
        assert KV.put(pid, :hello, :world) == :ok
        assert KV.get(pid, :hello) == :world
      end

  ## Tags

  The context is used to pass information from the callbacks to
  the test. In order to pass information from the test to the
  callback, ExUnit provides tags.

  By tagging a test, the tag value can be accessed in the context,
  allowing the developer to customize the test. Let's see an
  example:

      defmodule FileTest do
        # Changing directory cannot be async
        use ExUnit.Case, async: false

        setup context do
          # Read the :cd tag value
          if cd = context[:cd] do
            prev_cd = File.cwd!
            File.cd!(cd)
            on_exit fn -> File.cd!(prev_cd) end
          end

          :ok
        end

        @tag cd: "fixtures"
        test "reads utf-8 fixtures" do
          File.read("hello")
        end
      end

  In the example above, we have defined a tag called `:cd` that is
  read in the setup callback to configure the working directory the
  test is going to run on.

  Tags are also very effective when used with case templates
  (`ExUnit.CaseTemplate`) allowing callbacks in the case template
  to customize the test behaviour.

  Note a tag can be set in two different ways:

      @tag key: value
      @tag :key       # equivalent to setting @tag key: true

  If a tag is given more than once, the last value wins.

  ### Module tags

  A tag can be set for all tests in a module by setting `@moduletag`:

      @moduletag :external

  If the same key is set via `@tag`, the `@tag` value has higher
  precedence.

  ### Known tags

  The following tags are set automatically by ExUnit and are
  therefore reserved:

    * `:case` - the test case module
    * `:test` - the test name
    * `:line` - the line on which the test was defined
    * `:file` - the file on which the test was defined

  The following tags customize how tests behaves:

    * `:timeout` - customizes the test timeout in milliseconds (defaults to 30000)

  ## Filters

  Tags can also be used to identify specific tests, which can then
  be included or excluded using filters. The most common functionality
  is to exclude some particular tests from running, which can be done
  via `ExUnit.configure/1`:

      # Exclude all external tests from running
      ExUnit.configure(exclude: [external: true])

  From now on, ExUnit will not run any test that has the `external` flag
  set to true. This behaviour can be reversed with the `:include` option
  which is usually passed through the command line:

      mix test --include external:true

  Run `mix help test` for more information on how to run filters via Mix.

  Another use case for tags and filters is to exclude all tests that have
  a particular tag by default, regardless of its value, and include only
  a certain subset:

      ExUnit.configure(exclude: :os, include: [os: :unix])

  Keep in mind that all tests are included by default, so unless they are
  excluded first, the `include` option has no effect.
  """

  @doc false
  defmacro __using__(opts) do
    async = Keyword.get(opts, :async, false)

    unless Process.whereis(ExUnit.Server) do
      raise "cannot use ExUnit.Case without starting the ExUnit application, " <>
            "please call ExUnit.start() or explicitly start the :ex_unit app"
    end

    quote do
      unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
        if unquote(async) do
          ExUnit.Server.add_async_case(__MODULE__)
        else
          ExUnit.Server.add_sync_case(__MODULE__)
        end

        Enum.each [:ex_unit_tests, :tag, :moduletag],
          &Module.register_attribute(__MODULE__, &1, accumulate: true)

        @before_compile ExUnit.Case
        @ex_unit_test_names HashSet.new
        use ExUnit.Callbacks
      end

      import ExUnit.Callbacks
      import ExUnit.Assertions
      import ExUnit.Case
      import ExUnit.DocTest
    end
  end

  @doc """
  Define a test with a string.

  Provides a convenient macro that allows a test to be
  defined with a string. This macro automatically inserts
  the atom `:ok` as the last line of the test. That said,
  a passing test always returns `:ok`, but, more importantly,
  it forces Elixir to not tail call optimize the test and
  therefore avoids hiding lines from the backtrace.

  ## Examples

      test "true is equal to true" do
        assert true == true
      end

  """
  defmacro test(message, var \\ quote(do: _), contents) do
    contents =
      case contents do
        [do: block] ->
          quote do
            unquote(block)
            :ok
          end
        _ ->
          quote do
            try(unquote(contents))
            :ok
          end
      end

    var      = Macro.escape(var)
    contents = Macro.escape(contents, unquote: true)

    quote bind_quoted: binding do
      test = :"test #{message}"
      ExUnit.Case.__on_definition__(__ENV__, test)
      def unquote(test)(unquote(var)), do: unquote(contents)
    end
  end

  @doc false
  defmacro __before_compile__(_) do
    quote do
      def __ex_unit__(:case) do
        %ExUnit.TestCase{name: __MODULE__, tests: @ex_unit_tests}
      end
    end
  end

  @doc false
  def __on_definition__(env, name) do
    mod  = env.module
    tags = Module.get_attribute(mod, :tag) ++ Module.get_attribute(mod, :moduletag)
    tags = tags |> normalize_tags |> Map.merge(%{line: env.line, file: env.file})

    test = %ExUnit.Test{name: name, case: mod, tags: tags}
    test_names = Module.get_attribute(mod, :ex_unit_test_names)

    unless name in test_names do
      Module.put_attribute(mod, :ex_unit_tests, test)
      Module.put_attribute(mod, :ex_unit_test_names, HashSet.put(test_names, name))
    end

    Module.delete_attribute(mod, :tag)
  end

  defp normalize_tags(tags) do
    Enum.reduce Enum.reverse(tags), %{}, fn
      tag, acc when is_atom(tag) -> Map.put(acc, tag, true)
      tag, acc when is_list(tag) -> Dict.merge(acc, tag)
    end
  end
end


Skip test on Windows

Skip test on Windows

Code.require_file("../test_helper.exs", __DIR__)

defmodule Mix.RebarTest do
  use MixTest.Case

  # rebar_dep and git_rebar are loaded dynamically
  @compile {:no_warn_undefined, [:rebar_dep, :git_rebar]}

  defmodule RebarAsDep do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {:rebar_dep, path: MixTest.Case.tmp_path("rebar_dep"), app: false}
        ]
      ]
    end
  end

  defmodule RebarAsDepWithEnv do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {
            :rebar_dep,
            path: MixTest.Case.tmp_path("rebar_dep"),
            app: false,
            manager: :rebar,
            system_env: [{"FILE_FROM_ENV", "rebar-test-rebar"}, {"CONTENTS_FROM_ENV", "rebar"}]
          }
        ]
      ]
    end
  end

  defmodule Rebar3AsDep do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {
            :rebar_dep,
            path: MixTest.Case.tmp_path("rebar_dep"),
            app: false,
            manager: :rebar3,
            system_env: [{"FILE_FROM_ENV", "rebar-test-rebar3"}, {"CONTENTS_FROM_ENV", "rebar3"}]
          }
        ]
      ]
    end
  end

  defmodule RebarOverrideAsDep do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {
            :rebar_override,
            path: MixTest.Case.tmp_path("rebar_override"), app: false, manager: :rebar3
          }
        ]
      ]
    end
  end

  describe "load_config/1" do
    test "loads rebar.config" do
      path = MixTest.Case.fixture_path("rebar_dep")
      config = Mix.Rebar.load_config(path)
      assert config[:sub_dirs] == ['apps/*']
      assert config[:SCRIPT] == 'rebar.config.script'
    end

    test "loads rebar.config.script on dependency directory" do
      path = MixTest.Case.fixture_path("rebar_dep_script")
      config = Mix.Rebar.load_config(path)
      assert config[:dir] == {:ok, String.to_charlist(path)}
    end
  end

  @git_rebar_charlist '../../test/fixtures/git_rebar'
  @git_rebar_string "../../test/fixtures/git_rebar"

  describe "deps/1" do
    defp parse_dep(dep) do
      [deps: [dep]] |> Mix.Rebar.deps() |> hd()
    end

    test "parses Rebar dependencies" do
      assert parse_dep({:git_rebar, '~> 1.0'}) == {:git_rebar, "~> 1.0", override: true}

      assert parse_dep({:git_rebar, '~> 1.0', {:pkg, :rebar_fork}}) ==
               {:git_rebar, "~> 1.0", override: true, hex: :rebar_fork}

      assert parse_dep({:git_rebar, {:pkg, :rebar_fork}}) ==
               {:git_rebar, ">= 0.0.0", override: true, hex: :rebar_fork}

      assert parse_dep({:git_rebar, '0.1..*', {:git, @git_rebar_charlist, :master}}) ==
               {:git_rebar, ~r"0.1..*", override: true, git: @git_rebar_string, ref: "master"}

      assert parse_dep({:git_rebar, {:git, @git_rebar_charlist, :master}}) ==
               {:git_rebar, ">= 0.0.0", override: true, git: @git_rebar_string, ref: "master"}

      assert parse_dep({:git_rebar, '0.1..*', {:git, @git_rebar_charlist}, [:raw]}) ==
               {:git_rebar, ~r"0.1..*", override: true, git: @git_rebar_string, compile: false}

      assert parse_dep({:git_rebar, '', {:git, @git_rebar_charlist, {:ref, '64691eb'}}}) ==
               {:git_rebar, ~r"", override: true, git: @git_rebar_string, ref: "64691eb"}
    end
  end

  describe "apply_overrides/3" do
    test "applies overrides" do
      config = [deps: {:git_rebar, '~> 2.0'}]
      overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 1.0'}], overrides: overrides]

      config = [deps: [{:git_rebar, '~> 2.0'}]]
      overrides = [{:override, :bar, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 2.0'}], overrides: overrides]

      config = [deps: [{:git_rebar, '~> 2.0'}]]
      overrides = [{:override, :foo, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 1.0'}], overrides: overrides]

      config = [deps: [{:git_rebar, '~> 1.0'}]]
      overrides = [{:add, :foo, [deps: [{:git_rebar2, '~> 2.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar2, '~> 2.0'}, {:git_rebar, '~> 1.0'}], overrides: overrides]
    end

    test "concatenates overrides" do
      config = [deps: {:git_rebar, '~> 2.0'}, overrides: [{:add, :bar, []}]]
      overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 1.0'}], overrides: overrides ++ [{:add, :bar, []}]]
    end
  end

  describe "dependency_config/1" do
    test "converts Rebar config to dependency config" do
      config = Mix.Rebar.load_config(fixture_path("rebar_dep"))
      dep_config = Mix.Rebar.dependency_config(config)

      assert config[:erl_opts] == [:warnings_as_errors]
      assert config[:project_plugins] == [:remove_me]
      assert dep_config[:erl_opts] == []
      refute dep_config[:project_plugins]
    end
  end

  describe "recur/1" do
    test "recurs over sub dirs" do
      path = MixTest.Case.fixture_path("rebar_dep")

      File.cd!(path, fn ->
        config = Mix.Rebar.load_config(path)

        Mix.Rebar.recur(config, fn config ->
          if config[:sub_dirs] == ['from_apps_another'] do
            Process.put(:inside_apps_another, true)
          end
        end)
      end)

      unless Process.get(:inside_apps_another) do
        flunk("Expected inside_apps_another to return true")
      end
    end
  end

  describe "integration with Mix" do
    test "inherits Rebar manager" do
      Mix.Project.push(Rebar3AsDep)
      deps = Mix.Dep.load_on_environment([])
      assert Enum.all?(deps, &(&1.manager == :rebar3))
    end

    test "parses Rebar dependencies from rebar.config" do
      Mix.Project.push(RebarAsDep)

      deps = Mix.Dep.load_on_environment([])
      assert Enum.find(deps, &(&1.app == :rebar_dep))

      assert Enum.find(deps, fn %Mix.Dep{app: app, opts: opts} ->
               if app == :git_rebar do
                 assert Enum.find(opts, &match?({:git, _}, &1))
                 assert Enum.find(opts, &match?({:ref, "master"}, &1))
                 true
               end
             end)
    end

    test "handles Rebar overrides" do
      Mix.Project.push(RebarOverrideAsDep)

      in_tmp("Rebar overrides", fn ->
        Mix.Tasks.Deps.Get.run([])

        assert Mix.Dep.load_on_environment([]) |> Enum.map(& &1.app) ==
                 [:git_repo, :git_rebar, :rebar_override]
      end)
    after
      purge([GitRepo.MixProject])
    end

    test "gets and compiles dependencies for Rebar" do
      Mix.Project.push(RebarAsDepWithEnv)

      in_tmp("get and compile dependencies for Rebar", fn ->
        Mix.Tasks.Deps.Get.run([])
        assert_received {:mix_shell, :info, ["* Getting git_rebar" <> _]}

        Mix.Tasks.Deps.Compile.run([])
        assert_received {:mix_shell, :run, ["==> git_rebar (compile)\n"]}
        assert_received {:mix_shell, :run, ["==> rebar_dep (compile)\n"]}
        assert :git_rebar.any_function() == :ok
        assert :rebar_dep.any_function() == :ok

        load_paths =
          Mix.Dep.load_on_environment([])
          |> Enum.map(&Mix.Dep.load_paths(&1))
          |> Enum.concat()

        assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
        assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")

        # Assert we have no .mix/compile.lock as a .mix/compile.lock
        # means we check for the Elixir version on every command.
        refute File.exists?("_build/dev/lib/rebar_dep/.mix/compile.lock")
        refute File.exists?("_build/dev/lib/git_rebar/.mix/compile.lock")

        assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
        assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
      end)
    end

    # We run only on Unix because Windows has a hard time
    # removing the Rebar executable after executed.
    @tag [unix: true]
    test "applies variables from :system_env option when compiling dependencies for Rebar" do
      Mix.Project.push(RebarAsDepWithEnv)

      in_tmp("applies variables from system_env for Rebar", fn ->
        expected_file = Path.join(tmp_path("rebar_dep"), "rebar-test-rebar")
        File.rm(expected_file)

        Mix.Tasks.Deps.Get.run([])
        Mix.Tasks.Deps.Compile.run([])

        assert {:ok, "rebar"} = File.read(expected_file)
      end)
    end

    # We run only on Unix because Windows has a hard time
    # removing the Rebar executable after executed.
    @tag [unix: true]
    test "gets and compiles dependencies for Rebar3" do
      Mix.Project.push(Rebar3AsDep)

      in_tmp("get and compile dependencies for Rebar3", fn ->
        Mix.Tasks.Deps.Get.run([])
        assert_received {:mix_shell, :info, ["* Getting git_rebar " <> _]}

        Mix.Tasks.Deps.Compile.run([])
        assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
        assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
        assert :git_rebar.any_function() == :ok
        assert :rebar_dep.any_function() == :ok

        load_paths =
          Mix.Dep.load_on_environment([])
          |> Enum.map(&Mix.Dep.load_paths(&1))
          |> Enum.concat()

        assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
        assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")

        # Assert we have no .mix/compile.lock as a .mix/compile.lock
        # means we check for the Elixir version on every command.
        refute File.exists?("_build/dev/lib/rebar_dep/.mix/compile.lock")
        refute File.exists?("_build/dev/lib/git_rebar/.mix/compile.lock")

        assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
        assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
      end)
    end

    # We run only on Unix because Windows has a hard time
    # removing the Rebar executable after executed.
    @tag [unix: true]
    test "applies variables from :system_env option when compiling dependencies for Rebar3" do
      Mix.Project.push(Rebar3AsDep)

      in_tmp("applies variables from system_env for Rebar3", fn ->
        expected_file = Path.join(tmp_path("rebar_dep"), "rebar-test-rebar3")
        File.rm(expected_file)

        Mix.Tasks.Deps.Get.run([])
        Mix.Tasks.Deps.Compile.run([])

        assert {:ok, "rebar3"} = File.read(expected_file)
      end)
    end

    test "gets and compiles dependencies for Rebar with Mix" do
      Mix.Project.push(RebarAsDep)

      in_tmp("get and compile dependencies for Rebar with Mix", fn ->
        File.write!(MixTest.Case.tmp_path("rebar_dep/mix.exs"), """
        defmodule RebarDep.MixProject do
          use Mix.Project

          def project do
            [app: :rebar_dep,
             version: "0.0.1"]
          end
        end
        """)

        Mix.Tasks.Deps.Compile.run([])
        assert_received {:mix_shell, :info, ["==> rebar_dep"]}
        assert_received {:mix_shell, :info, ["Generated rebar_dep app"]}
        assert File.regular?("_build/dev/lib/rebar_dep/ebin/rebar_dep.app")
      end)
    after
      File.rm(MixTest.Case.tmp_path("rebar_dep/mix.exs"))
    end
  end
end

Code.require_file("../test_helper.exs", __DIR__)

defmodule Mix.RebarTest do
  use MixTest.Case

  # rebar_dep and git_rebar are loaded dynamically
  @compile {:no_warn_undefined, [:rebar_dep, :git_rebar]}

  defmodule RebarAsDep do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {:rebar_dep, path: MixTest.Case.tmp_path("rebar_dep"), app: false}
        ]
      ]
    end
  end

  defmodule RebarAsDepWithEnv do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {
            :rebar_dep,
            path: MixTest.Case.tmp_path("rebar_dep"),
            app: false,
            manager: :rebar,
            system_env: [{"FILE_FROM_ENV", "rebar-test-rebar"}, {"CONTENTS_FROM_ENV", "rebar"}]
          }
        ]
      ]
    end
  end

  defmodule Rebar3AsDep do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {
            :rebar_dep,
            path: MixTest.Case.tmp_path("rebar_dep"),
            app: false,
            manager: :rebar3,
            system_env: [{"FILE_FROM_ENV", "rebar-test-rebar3"}, {"CONTENTS_FROM_ENV", "rebar3"}]
          }
        ]
      ]
    end
  end

  defmodule RebarOverrideAsDep do
    def project do
      [
        app: :rebar_as_dep,
        version: "0.1.0",
        deps: [
          {
            :rebar_override,
            path: MixTest.Case.tmp_path("rebar_override"), app: false, manager: :rebar3
          }
        ]
      ]
    end
  end

  describe "load_config/1" do
    test "loads rebar.config" do
      path = MixTest.Case.fixture_path("rebar_dep")
      config = Mix.Rebar.load_config(path)
      assert config[:sub_dirs] == ['apps/*']
      assert config[:SCRIPT] == 'rebar.config.script'
    end

    test "loads rebar.config.script on dependency directory" do
      path = MixTest.Case.fixture_path("rebar_dep_script")
      config = Mix.Rebar.load_config(path)
      assert config[:dir] == {:ok, String.to_charlist(path)}
    end
  end

  @git_rebar_charlist '../../test/fixtures/git_rebar'
  @git_rebar_string "../../test/fixtures/git_rebar"

  describe "deps/1" do
    defp parse_dep(dep) do
      [deps: [dep]] |> Mix.Rebar.deps() |> hd()
    end

    test "parses Rebar dependencies" do
      assert parse_dep({:git_rebar, '~> 1.0'}) == {:git_rebar, "~> 1.0", override: true}

      assert parse_dep({:git_rebar, '~> 1.0', {:pkg, :rebar_fork}}) ==
               {:git_rebar, "~> 1.0", override: true, hex: :rebar_fork}

      assert parse_dep({:git_rebar, {:pkg, :rebar_fork}}) ==
               {:git_rebar, ">= 0.0.0", override: true, hex: :rebar_fork}

      assert parse_dep({:git_rebar, '0.1..*', {:git, @git_rebar_charlist, :master}}) ==
               {:git_rebar, ~r"0.1..*", override: true, git: @git_rebar_string, ref: "master"}

      assert parse_dep({:git_rebar, {:git, @git_rebar_charlist, :master}}) ==
               {:git_rebar, ">= 0.0.0", override: true, git: @git_rebar_string, ref: "master"}

      assert parse_dep({:git_rebar, '0.1..*', {:git, @git_rebar_charlist}, [:raw]}) ==
               {:git_rebar, ~r"0.1..*", override: true, git: @git_rebar_string, compile: false}

      assert parse_dep({:git_rebar, '', {:git, @git_rebar_charlist, {:ref, '64691eb'}}}) ==
               {:git_rebar, ~r"", override: true, git: @git_rebar_string, ref: "64691eb"}
    end
  end

  describe "apply_overrides/3" do
    test "applies overrides" do
      config = [deps: {:git_rebar, '~> 2.0'}]
      overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 1.0'}], overrides: overrides]

      config = [deps: [{:git_rebar, '~> 2.0'}]]
      overrides = [{:override, :bar, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 2.0'}], overrides: overrides]

      config = [deps: [{:git_rebar, '~> 2.0'}]]
      overrides = [{:override, :foo, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 1.0'}], overrides: overrides]

      config = [deps: [{:git_rebar, '~> 1.0'}]]
      overrides = [{:add, :foo, [deps: [{:git_rebar2, '~> 2.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar2, '~> 2.0'}, {:git_rebar, '~> 1.0'}], overrides: overrides]
    end

    test "concatenates overrides" do
      config = [deps: {:git_rebar, '~> 2.0'}, overrides: [{:add, :bar, []}]]
      overrides = [{:override, [deps: [{:git_rebar, '~> 1.0'}]]}]

      assert Mix.Rebar.apply_overrides(:foo, config, overrides) ==
               [deps: [{:git_rebar, '~> 1.0'}], overrides: overrides ++ [{:add, :bar, []}]]
    end
  end

  describe "dependency_config/1" do
    test "converts Rebar config to dependency config" do
      config = Mix.Rebar.load_config(fixture_path("rebar_dep"))
      dep_config = Mix.Rebar.dependency_config(config)

      assert config[:erl_opts] == [:warnings_as_errors]
      assert config[:project_plugins] == [:remove_me]
      assert dep_config[:erl_opts] == []
      refute dep_config[:project_plugins]
    end
  end

  describe "recur/1" do
    test "recurs over sub dirs" do
      path = MixTest.Case.fixture_path("rebar_dep")

      File.cd!(path, fn ->
        config = Mix.Rebar.load_config(path)

        Mix.Rebar.recur(config, fn config ->
          if config[:sub_dirs] == ['from_apps_another'] do
            Process.put(:inside_apps_another, true)
          end
        end)
      end)

      unless Process.get(:inside_apps_another) do
        flunk("Expected inside_apps_another to return true")
      end
    end
  end

  describe "integration with Mix" do
    test "inherits Rebar manager" do
      Mix.Project.push(Rebar3AsDep)
      deps = Mix.Dep.load_on_environment([])
      assert Enum.all?(deps, &(&1.manager == :rebar3))
    end

    test "parses Rebar dependencies from rebar.config" do
      Mix.Project.push(RebarAsDep)

      deps = Mix.Dep.load_on_environment([])
      assert Enum.find(deps, &(&1.app == :rebar_dep))

      assert Enum.find(deps, fn %Mix.Dep{app: app, opts: opts} ->
               if app == :git_rebar do
                 assert Enum.find(opts, &match?({:git, _}, &1))
                 assert Enum.find(opts, &match?({:ref, "master"}, &1))
                 true
               end
             end)
    end

    test "handles Rebar overrides" do
      Mix.Project.push(RebarOverrideAsDep)

      in_tmp("Rebar overrides", fn ->
        Mix.Tasks.Deps.Get.run([])

        assert Mix.Dep.load_on_environment([]) |> Enum.map(& &1.app) ==
                 [:git_repo, :git_rebar, :rebar_override]
      end)
    after
      purge([GitRepo.MixProject])
    end

    test "gets and compiles dependencies for Rebar" do
      Mix.Project.push(RebarAsDepWithEnv)

      in_tmp("get and compile dependencies for Rebar", fn ->
        Mix.Tasks.Deps.Get.run([])
        assert_received {:mix_shell, :info, ["* Getting git_rebar" <> _]}

        Mix.Tasks.Deps.Compile.run([])
        assert_received {:mix_shell, :run, ["==> git_rebar (compile)\n"]}
        assert_received {:mix_shell, :run, ["==> rebar_dep (compile)\n"]}
        assert :git_rebar.any_function() == :ok
        assert :rebar_dep.any_function() == :ok

        load_paths =
          Mix.Dep.load_on_environment([])
          |> Enum.map(&Mix.Dep.load_paths(&1))
          |> Enum.concat()

        assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
        assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")

        # Assert we have no .mix/compile.lock as a .mix/compile.lock
        # means we check for the Elixir version on every command.
        refute File.exists?("_build/dev/lib/rebar_dep/.mix/compile.lock")
        refute File.exists?("_build/dev/lib/git_rebar/.mix/compile.lock")

        assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
        assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
      end)
    end

    # We run only on Unix because Windows has a hard time
    # removing the Rebar executable after executed.
    @tag [unix: true]
    test "applies variables from :system_env option when compiling dependencies for Rebar" do
      Mix.Project.push(RebarAsDepWithEnv)

      in_tmp("applies variables from system_env for Rebar", fn ->
        expected_file = Path.join(tmp_path("rebar_dep"), "rebar-test-rebar")
        File.rm(expected_file)

        Mix.Tasks.Deps.Get.run([])
        Mix.Tasks.Deps.Compile.run([])

        assert {:ok, "rebar"} = File.read(expected_file)
      end)
    end

    test "gets and compiles dependencies for Rebar3" do
      Mix.Project.push(Rebar3AsDep)

      in_tmp("get and compile dependencies for Rebar3", fn ->
        Mix.Tasks.Deps.Get.run([])
        assert_received {:mix_shell, :info, ["* Getting git_rebar " <> _]}

        Mix.Tasks.Deps.Compile.run([])
        assert_received {:mix_shell, :run, ["===> Compiling git_rebar\n"]}
        assert_received {:mix_shell, :run, ["===> Compiling rebar_dep\n"]}
        assert :git_rebar.any_function() == :ok
        assert :rebar_dep.any_function() == :ok

        load_paths =
          Mix.Dep.load_on_environment([])
          |> Enum.map(&Mix.Dep.load_paths(&1))
          |> Enum.concat()

        assert File.exists?("_build/dev/lib/rebar_dep/ebin/rebar_dep.beam")
        assert File.exists?("_build/dev/lib/git_rebar/ebin/git_rebar.beam")

        # Assert we have no .mix/compile.lock as a .mix/compile.lock
        # means we check for the Elixir version on every command.
        refute File.exists?("_build/dev/lib/rebar_dep/.mix/compile.lock")
        refute File.exists?("_build/dev/lib/git_rebar/.mix/compile.lock")

        assert Enum.any?(load_paths, &String.ends_with?(&1, "git_rebar/ebin"))
        assert Enum.any?(load_paths, &String.ends_with?(&1, "rebar_dep/ebin"))
      end)
    end

    # We run only on Unix because Windows has a hard time
    # removing the Rebar executable after executed.
    @tag [unix: true]
    test "applies variables from :system_env option when compiling dependencies for Rebar3" do
      Mix.Project.push(Rebar3AsDep)

      in_tmp("applies variables from system_env for Rebar3", fn ->
        expected_file = Path.join(tmp_path("rebar_dep"), "rebar-test-rebar3")
        File.rm(expected_file)

        Mix.Tasks.Deps.Get.run([])
        Mix.Tasks.Deps.Compile.run([])

        assert {:ok, "rebar3"} = File.read(expected_file)
      end)
    end

    test "gets and compiles dependencies for Rebar with Mix" do
      Mix.Project.push(RebarAsDep)

      in_tmp("get and compile dependencies for Rebar with Mix", fn ->
        File.write!(MixTest.Case.tmp_path("rebar_dep/mix.exs"), """
        defmodule RebarDep.MixProject do
          use Mix.Project

          def project do
            [app: :rebar_dep,
             version: "0.0.1"]
          end
        end
        """)

        Mix.Tasks.Deps.Compile.run([])
        assert_received {:mix_shell, :info, ["==> rebar_dep"]}
        assert_received {:mix_shell, :info, ["Generated rebar_dep app"]}
        assert File.regular?("_build/dev/lib/rebar_dep/ebin/rebar_dep.app")
      end)
    after
      File.rm(MixTest.Case.tmp_path("rebar_dep/mix.exs"))
    end
  end
end


Revert "Increased time for init test"

Revert "Increased time for init test"

This reverts commit 28af3b66131bfa64578eb7c9b136a660564553e2.

require Logger

defmodule InitTest do
  use ExUnit.Case, async: false
  # @moduletag :capture_log

  alias Test.Client

  setup do
    # Explicitly get a connection before each test
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Serverboards.Repo)
    # Setting the shared mode must be done only after checkout
    Ecto.Adapters.SQL.Sandbox.mode(Serverboards.Repo, {:shared, self()})
  end

  test "Run init" do
    init = %Serverboards.Plugin.Init{
      command: "serverboards.test.auth/init.cmd2",
      call: "init",
      id: "test init"
    }

    # WARNING may fail as timers are a bit tight
    {:ok, pid} = Serverboards.Plugin.Init.Supervisor.start_init(init)
    :timer.sleep(100)
    assert Serverboards.Plugin.Runner.status(init.command) == :running
    :timer.sleep(1500)
    # timeout, plugin runner killed the command
    assert Serverboards.Plugin.Runner.status(init.command) == :not_running
    :timer.sleep(1200)
    # and timeout to recover it (min 1s)
    Logger.debug("Ready? normally was 2s wait")
    assert Serverboards.Plugin.Runner.status(init.command) == :running
  end

  test "Run failing init" do
    init = %Serverboards.Plugin.Init{
      command: "serverboards.test.auth/init.cmd2",
      call: "fail",
      id: "test fail"
    }
    Serverboards.Plugin.Init.Supervisor.start_init(init)
    :timer.sleep(100)
    assert Serverboards.Plugin.Runner.status(init.command) == :running
    :timer.sleep(1500)
    assert Serverboards.Plugin.Runner.status(init.command) == :not_running
  end

  test "Init reloads when plugins are modified" do
    init = %Serverboards.Plugin.Init{
      command: "serverboards.test.auth/init.cmd2",
      call: "init",
      id: "test.reload"
    }

    {:ok, pid} = Serverboards.Plugin.Init.Supervisor.start_init(init)
    :timer.sleep(100)

    Serverboards.Event.emit("plugins_reload", [])
    :timer.sleep(100)

    # still running, it issued a stop (not kill), it will timeout anyway later
    psux = :os.cmd('ps ux | grep -v grep | grep init2.py')
    Logger.debug("#{inspect psux}")
    assert psux != []

    # as it is not a real init, was started manually, should have been removed,
    # and not started
    :timer.sleep(900)
    psux = :os.cmd('ps ux | grep -v grep | grep init2.py')
    Logger.debug("#{inspect psux}")
    assert psux == []

  end
end

require Logger

defmodule InitTest do
  use ExUnit.Case, async: false
  # @moduletag :capture_log

  alias Test.Client

  setup do
    # Explicitly get a connection before each test
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Serverboards.Repo)
    # Setting the shared mode must be done only after checkout
    Ecto.Adapters.SQL.Sandbox.mode(Serverboards.Repo, {:shared, self()})
  end

  test "Run init" do
    init = %Serverboards.Plugin.Init{
      command: "serverboards.test.auth/init.cmd2",
      call: "init",
      id: "test init"
    }

    # WARNING may fail as timers are a bit tight
    {:ok, pid} = Serverboards.Plugin.Init.Supervisor.start_init(init)
    :timer.sleep(100)
    assert Serverboards.Plugin.Runner.status(init.command) == :running
    :timer.sleep(2500)
    # timeout, plugin runner killed the command
    assert Serverboards.Plugin.Runner.status(init.command) == :not_running
    :timer.sleep(1200)
    # and timeout to recover it (min 1s)
    Logger.debug("Ready? normally was 2s wait")
    assert Serverboards.Plugin.Runner.status(init.command) == :running
  end

  test "Run failing init" do
    init = %Serverboards.Plugin.Init{
      command: "serverboards.test.auth/init.cmd2",
      call: "fail",
      id: "test fail"
    }
    Serverboards.Plugin.Init.Supervisor.start_init(init)
    :timer.sleep(100)
    assert Serverboards.Plugin.Runner.status(init.command) == :running
    :timer.sleep(1500)
    assert Serverboards.Plugin.Runner.status(init.command) == :not_running
  end

  test "Init reloads when plugins are modified" do
    init = %Serverboards.Plugin.Init{
      command: "serverboards.test.auth/init.cmd2",
      call: "init",
      id: "test.reload"
    }

    {:ok, pid} = Serverboards.Plugin.Init.Supervisor.start_init(init)
    :timer.sleep(100)

    Serverboards.Event.emit("plugins_reload", [])
    :timer.sleep(100)

    # still running, it issued a stop (not kill), it will timeout anyway later
    psux = :os.cmd('ps ux | grep -v grep | grep init2.py')
    Logger.debug("#{inspect psux}")
    assert psux != []

    # as it is not a real init, was started manually, should have been removed,
    # and not started
    :timer.sleep(900)
    psux = :os.cmd('ps ux | grep -v grep | grep init2.py')
    Logger.debug("#{inspect psux}")
    assert psux == []

  end
end


handle info

handle info

defmodule Logger.Backends.ExceptionNotification do
  @behaviour :gen_event

  def init(_) do
    {:ok, %{}}
  end

  def handle_call({:configure, _options}, _state) do
    {:ok, :ok, %{}}
  end

  def handle_info(_level, state) do
    {:noreply, state}
  end

  def handle_event({:error, _group_leader, {Logger, message, timestamp, metadata}}, state) do
    unless String.contains?(inspect(message), ["Fatal error: handshake failure",
                                               "Warning: unrecognised name",
                                               ":unexpected_end"]) do
      Logger.Formatter.compile("$time $metadata[$level] $message\n")
      |> Logger.Formatter.format(:error, message, timestamp, metadata)
      |> IO.iodata_to_binary
      |> Pan.Email.error_notification("exeception_notification@panoptikum.io",
                                      "stefan@panoptikum.io")
      |> Pan.Mailer.deliver_now()
    end

    {:ok, state}
  end

  def handle_event({_level, group_leader, {Logger, _, _, _}}, state)
    when node(group_leader) != node(), do: {:ok, state}

  def handle_event({level, _group_leader, {Logger, _, _, _}}, state)
    when level in [:debug, :info, :warn], do: {:ok, state}
end

defmodule Logger.Backends.ExceptionNotification do
  @behaviour :gen_event

  def init(_) do
    {:ok, %{}}
  end

  def handle_call({:configure, _options}, _state) do
    {:ok, :ok, %{}}
  end

  def handle_event({:error, _group_leader, {Logger, message, timestamp, metadata}}, state) do
    unless String.contains?(inspect(message), ["Fatal error: handshake failure",
                                               "Warning: unrecognised name",
                                               ":unexpected_end"]) do
      Logger.Formatter.compile("$time $metadata[$level] $message\n")
      |> Logger.Formatter.format(:error, message, timestamp, metadata)
      |> IO.iodata_to_binary
      |> Pan.Email.error_notification("exeception_notification@panoptikum.io",
                                      "stefan@panoptikum.io")
      |> Pan.Mailer.deliver_now()
    end

    {:ok, state}
  end

  def handle_event({_level, group_leader, {Logger, _, _, _}}, state)
    when node(group_leader) != node(), do: {:ok, state}

  def handle_event({level, _group_leader, {Logger, _, _, _}}, state)
    when level in [:debug, :info, :warn], do: {:ok, state}
end


Fix formatter tests

Fix formatter tests

defmodule Mix.Tasks.Phx.Gen.Auth.HashingLibrary do
  @moduledoc false

  defstruct [:module, :mix_dependency, :test_config]

  def build("bcrypt") do
    lib = %__MODULE__{
      module: Bcrypt,
      mix_dependency: ~s|{:bcrypt_elixir, "~> 2.0\"}|,
      test_config: """
      config :bcrypt_elixir, :log_rounds, 1
      """
    }

    {:ok, lib}
  end

  def build("pbkdf2") do
    lib = %__MODULE__{
      module: Pbkdf2,
      mix_dependency: ~s|{:pbkdf2_elixir, "~> 1.0\"}|,
      test_config: """
      config :pbkdf2_elixir, :rounds, 1
      """
    }

    {:ok, lib}
  end

  def build("argon2") do
    lib = %__MODULE__{
      module: Argon2,
      mix_dependency: ~s|{:argon2_elixir, "~> 2.0\"}|,
      test_config: """
      config :argon2_elixir, t_cost: 1, m_cost: 8
      """
    }

    {:ok, lib}
  end

  def build(other) do
    {:error, {:unknown_library, other}}
  end
end


Really need to add tests to check this shit man...

defmodule SlackCoder.Github.Watchers.Repository do
  use GenServer
  import SlackCoder.Github.Helper
  alias SlackCoder.Models.PR
  import Ecto.Changeset, only: [put_change: 3]
  use Timex
  require Logger
  alias SlackCoder.Repo

  @poll_minutes 5
  @poll_interval 60_000 * @poll_minutes # 5 minutes

  def start_link(repo) do
    GenServer.start_link __MODULE__, repo
  end

  def init(repo) do
    pulls(repo) # Async fetch
    :timer.send_interval @poll_interval, :update_prs
    {:ok, {repo, []}}
  end

  def handle_info({:pr_response, prs}, {repo, old_prs}) do
    prs # new prs start watchers for each
    |> Enum.each(&SlackCoder.Github.Supervisor.start_watcher(&1))
    close_prs(prs, old_prs)
    if can_send_notifications? do
      prs = prs |> Enum.map(&(stale_pr(&1)))
    end
    {:noreply, {repo, prs}}
  end

  def handle_info(:update_prs, {repo, existing_prs}) do
    pulls(repo, existing_prs)
    # State doesn't change until :pr_response message is received
    {:noreply, {repo, existing_prs}}
  end

  def poll_minutes(), do: @poll_minutes

  defp close_prs(_new_prs, []), do: nil
  defp close_prs(new_prs, old_prs) do
    closed_prs =  (old_prs |> Enum.map(&(&1.number))) -- (new_prs |> Enum.map(&(&1.number)))
    if closed_prs != [] do
      Logger.debug "Closed PRs: #{inspect closed_prs}"
      Enum.each closed_prs, fn(pr_number)->
        pr = Enum.find(old_prs, &( &1.number == pr_number))
        Logger.debug "Stopping watcher for: PR-#{pr.number} #{pr.title}"
        SlackCoder.Github.Supervisor.stop_watcher(pr)
        SlackCoder.Endpoint.broadcast("prs:all", "pr:remove", %{pr: pr.number})
        [message_for | slack_users] = slack_user_with_monitors(pr)
        response = get("repos/#{pr.owner}/#{pr.repo}/pulls/#{pr.number}")
        merged = response["merged"] || response["merged_at"] != nil
        PR.changeset(pr, %{merged_at: response["merged_at"], closed_at: response["closed_at"]})
          |> Repo.update
        if merged do
          message = """
          :smiling_imp: _MERGED_ *#{pr.title}* :raveparrot:
          #{pr.html_url}
          """
          notify(slack_users, :merge, message_for, message)
        else
          message = """
          :rage: _CLOSED_ *#{pr.title}*
          #{pr.html_url}
          """
          notify(slack_users, :close, message_for, message)
        end
      end
    end
  end

  def stale_pr(pr) do
    latest_comment = find_latest_comment(pr) || pr.opened_at
    pr_latest_comment = pr.latest_comment || pr.opened_at
    cs = PR.changeset(pr, %{latest_comment: latest_comment})
    hours = Date.diff(latest_comment, now, :hours)
    if hours > pr.backoff && can_send_notifications? do
      backoff = next_backoff(pr.backoff, hours)
      cs = put_change(cs, :backoff, backoff)
      send_notification = true
    end
    if Date.compare(latest_comment, pr_latest_comment) != 0 do
      cs = put_change(cs, :backoff, Application.get_env(:slack_coder, :pr_backoff_start, 1))
    end
    {:ok, pr} = SlackCoder.Repo.update(cs)
    if send_notification, do: stale_pr_notification(pr)
    pr
  end

  def next_backoff(backoff, greater_than) do
    next_exponent = trunc(:math.log2(backoff) + 1)
    next_notification = trunc(:math.pow(2, next_exponent))
    if next_notification > greater_than do
      next_notification
    else
      next_backoff(next_notification, greater_than)
    end
  end

  defp stale_pr_notification(pr) do
    stale_hours = Timex.Date.diff(pr.latest_comment, now, :hours)
    [message_for | slack_users] = slack_user_with_monitors(pr)
    message = """
    :hankey: *#{pr.title}*
    Stale for *#{stale_hours}* hours
    #{pr.html_url}
    """
    notify(slack_users, :stale, message_for, message)
  end

end

defmodule SlackCoder.Github.Watchers.Repository do
  use GenServer
  import SlackCoder.Github.Helper
  alias SlackCoder.Models.PR
  import Ecto.Changeset, only: [put_change: 3]
  use Timex
  require Logger

  @poll_minutes 5
  @poll_interval 60_000 * @poll_minutes # 5 minutes

  def start_link(repo) do
    GenServer.start_link __MODULE__, repo
  end

  def init(repo) do
    pulls(repo) # Async fetch
    :timer.send_interval @poll_interval, :update_prs
    {:ok, {repo, []}}
  end

  def handle_info({:pr_response, prs}, {repo, old_prs}) do
    prs # new prs start watchers for each
    |> Enum.each(&SlackCoder.Github.Supervisor.start_watcher(&1))
    close_prs(prs, old_prs)
    if can_send_notifications? do
      prs = prs |> Enum.map(&(stale_pr(&1)))
    end
    {:noreply, {repo, prs}}
  end

  def handle_info(:update_prs, {repo, existing_prs}) do
    pulls(repo, existing_prs)
    # State doesn't change until :pr_response message is received
    {:noreply, {repo, existing_prs}}
  end

  def poll_minutes(), do: @poll_minutes

  defp close_prs(_new_prs, []), do: nil
  defp close_prs(new_prs, old_prs) do
    closed_prs =  (old_prs |> Enum.map(&(&1.number))) -- (new_prs |> Enum.map(&(&1.number)))
    if closed_prs != [] do
      Logger.debug "Closed PRs: #{inspect closed_prs}"
      Enum.each closed_prs, fn(pr_number)->
        pr = Enum.find(old_prs, &( &1.number == pr_number))
        Logger.debug "Stopping watcher for: PR-#{pr.number} #{pr.title}"
        SlackCoder.Github.Supervisor.stop_watcher(pr)
        SlackCoder.Endpoint.broadcast("prs:all", "pr:remove", %{pr: pr.number})
        [message_for | slack_users] = slack_user_with_monitors(pr)
        response = get("repos/#{pr.owner}/#{pr.repo}/pulls/#{pr.number}")
        merged = response["merged"] || response["merged_at"] != nil
        PR.changeset(pr, %{merged_at: response["merged_at"], closed_at: response["closed_at"]})
          |> Repo.update
        if merged do
          message = """
          :smiling_imp: _MERGED_ *#{pr.title}* :raveparrot:
          #{pr.html_url}
          """
          notify(slack_users, :merge, message_for, message)
        else
          message = """
          :rage: _CLOSED_ *#{pr.title}*
          #{pr.html_url}
          """
          notify(slack_users, :close, message_for, message)
        end
      end
    end
  end

  def stale_pr(pr) do
    latest_comment = find_latest_comment(pr) || pr.opened_at
    pr_latest_comment = pr.latest_comment || pr.opened_at
    cs = PR.changeset(pr, %{latest_comment: latest_comment})
    hours = Date.diff(latest_comment, now, :hours)
    if hours > pr.backoff && can_send_notifications? do
      backoff = next_backoff(pr.backoff, hours)
      cs = put_change(cs, :backoff, backoff)
      send_notification = true
    end
    if Date.compare(latest_comment, pr_latest_comment) != 0 do
      cs = put_change(cs, :backoff, Application.get_env(:slack_coder, :pr_backoff_start, 1))
    end
    {:ok, pr} = SlackCoder.Repo.update(cs)
    if send_notification, do: stale_pr_notification(pr)
    pr
  end

  def next_backoff(backoff, greater_than) do
    next_exponent = trunc(:math.log2(backoff) + 1)
    next_notification = trunc(:math.pow(2, next_exponent))
    if next_notification > greater_than do
      next_notification
    else
      next_backoff(next_notification, greater_than)
    end
  end

  defp stale_pr_notification(pr) do
    stale_hours = Timex.Date.diff(pr.latest_comment, now, :hours)
    [message_for | slack_users] = slack_user_with_monitors(pr)
    message = """
    :hankey: *#{pr.title}*
    Stale for *#{stale_hours}* hours
    #{pr.html_url}
    """
    notify(slack_users, :stale, message_for, message)
  end

end


Add additional documentation for `assert_in_delta` and `refute_in_delta` (#7191)

defmodule ExUnit.AssertionError do
  @moduledoc """
  Raised to signal an assertion error.
  """

  @no_value :ex_unit_no_meaningful_value

  defexception left: @no_value, right: @no_value, message: @no_value, expr: @no_value

  @doc """
  Indicates no meaningful value for a field.
  """
  def no_value do
    @no_value
  end

  def message(exception) do
    "\n\n" <> ExUnit.Formatter.format_assertion_error(exception)
  end
end

defmodule ExUnit.MultiError do
  @moduledoc """
  Raised to signal multiple errors happened in a test case.
  """

  defexception errors: []

  def message(%{errors: errors}) do
    "got the following errors:\n\n" <>
      Enum.map_join(errors, "\n\n", fn {kind, error, stack} ->
        Exception.format_banner(kind, error, stack)
      end)
  end
end

defmodule ExUnit.Assertions do
  @moduledoc """
  This module contains a set of assertion functions that are
  imported by default into your test cases.

  In general, a developer will want to use the general
  `assert` macro in tests. This macro introspects your code
  and provides good reporting whenever there is a failure.
  For example, `assert some_fun() == 10` will fail (assuming
  `some_fun()` returns `13`):

      Comparison (using ==) failed in:
      code:  some_fun() == 10
      left:  13
      right: 10

  This module also provides other convenience functions
  like `assert_in_delta` and `assert_raise` to easily handle
  other common cases such as checking a floating-point number
  or handling exceptions.
  """

  @doc """
  Asserts its argument is a truthy value.

  `assert` introspects the underlying expression and provides
  good reporting whenever there is a failure. For example,
  if the expression uses the comparison operator, the message
  will show the values of the two sides. The assertion

      assert 1 + 2 + 3 + 4 > 15

   will fail with the message:

      Assertion with > failed
      code:  1 + 2 + 3 + 4 > 15
      left:  10
      right: 15

  Similarly, if a match expression is given, it will report
  any failure in terms of that match. Given

      assert [1] = [2]

  you'll see:

      match (=) failed
      code:  [1] = [2]
      right: [2]

  Keep in mind that `assert` does not change its semantics
  based on the expression. In other words, the expression
  is still required to return a truthy value. For example,
  the following will fail:

      assert nil = some_function_that_returns_nil()

  Even though the match works, `assert` still expects a truth
  value. In such cases, simply use `Kernel.==/2` or
  `Kernel.match?/2`.
  """
  defmacro assert({:=, _, [left, right]} = assertion) do
    code = escape_quoted(:assert, assertion)

    left = expand_pattern(left, __CALLER__)
    vars = collect_vars_from_pattern(left)
    pins = collect_pins_from_pattern(left, __CALLER__.vars)

    # If the match works, we need to check if the value
    # is not nil nor false. We need to rewrite the if
    # to avoid silly warnings though.
    return =
      suppress_warning(
        quote do
          case right do
            x when x in [nil, false] ->
              raise ExUnit.AssertionError,
                expr: expr,
                message: "Expected truthy, got #{inspect(right)}"

            _ ->
              :ok
          end
        end
      )

    match_expr =
      suppress_warning(
        quote do
          case right do
            unquote(left) ->
              unquote(return)
              unquote(vars)

            _ ->
              raise ExUnit.AssertionError,
                right: right,
                expr: expr,
                message: "match (=) failed" <> ExUnit.Assertions.__pins__(unquote(pins))
          end
        end
      )

    quote do
      right = unquote(right)
      expr = unquote(code)
      unquote(vars) = unquote(match_expr)
      right
    end
  end

  defmacro assert({:match?, meta, [left, right]} = assertion) do
    code = escape_quoted(:assert, assertion)
    match? = {:match?, meta, [left, Macro.var(:right, __MODULE__)]}
    pins = collect_pins_from_pattern(left, __CALLER__.vars)

    quote do
      right = unquote(right)

      assert unquote(match?),
        right: right,
        expr: unquote(code),
        message: "match (match?) failed" <> ExUnit.Assertions.__pins__(unquote(pins))
    end
  end

  defmacro assert(assertion) do
    case translate_assertion(:assert, assertion, __CALLER__) do
      nil ->
        quote do
          value = unquote(assertion)

          unless value do
            raise ExUnit.AssertionError,
              expr: unquote(escape_quoted(:assert, assertion)),
              message: "Expected truthy, got #{inspect(value)}"
          end

          value
        end

      value ->
        value
    end
  end

  @doc """
  A negative assertion, expects the expression to be `false` or `nil`.

  Keep in mind that `refute` does not change the semantics of
  the given expression. In other words, the following will fail:

      refute {:ok, _} = some_function_that_returns_error_tuple()

  The code above will fail because the `=` operator always fails
  when the sides do not match and `refute/2` does not change it.

  The correct way to write the refutation above is to use
  `Kernel.match?/2`:

      refute match? {:ok, _}, some_function_that_returns_error_tuple()

  ## Examples

      refute age < 0

  """
  defmacro refute({:match?, meta, [left, right]} = assertion) do
    code = escape_quoted(:refute, assertion)
    match? = {:match?, meta, [left, Macro.var(:right, __MODULE__)]}
    pins = collect_pins_from_pattern(left, __CALLER__.vars)

    quote do
      right = unquote(right)

      refute unquote(match?),
        right: right,
        expr: unquote(code),
        message:
          "match (match?) succeeded, but should have failed" <>
            ExUnit.Assertions.__pins__(unquote(pins))
    end
  end

  defmacro refute(assertion) do
    case translate_assertion(:refute, assertion, __CALLER__) do
      nil ->
        quote do
          value = unquote(assertion)

          if value do
            raise ExUnit.AssertionError,
              expr: unquote(escape_quoted(:refute, assertion)),
              message: "Expected false or nil, got #{inspect(value)}"
          end

          value
        end

      value ->
        {:!, [], [value]}
    end
  end

  ## START HELPERS

  @operator [:==, :<, :>, :<=, :>=, :===, :=~, :!==, :!=, :in]

  defp translate_assertion(:assert, {operator, meta, [_, _]} = expr, caller)
       when operator in @operator do
    left = Macro.var(:left, __MODULE__)
    right = Macro.var(:right, __MODULE__)
    call = {operator, meta, [left, right]}
    equality_check? = operator in [:<, :>, :!==, :!=]
    message = "Assertion with #{operator} failed"
    translate_assertion(:assert, expr, call, message, equality_check?, caller)
  end

  defp translate_assertion(:refute, {operator, meta, [_, _]} = expr, caller)
       when operator in @operator do
    left = Macro.var(:left, __MODULE__)
    right = Macro.var(:right, __MODULE__)
    call = {:not, meta, [{operator, meta, [left, right]}]}
    equality_check? = operator in [:<=, :>=, :===, :==, :=~]
    message = "Refute with #{operator} failed"
    translate_assertion(:refute, expr, call, message, equality_check?, caller)
  end

  defp translate_assertion(_kind, _expected, _caller) do
    nil
  end

  defp translate_assertion(kind, {_, _, [left, right]} = expr, call, message, true, _caller) do
    expr = escape_quoted(kind, expr)

    quote do
      left = unquote(left)
      right = unquote(right)

      if ExUnit.Assertions.__equal__?(left, right) do
        assert false,
          left: left,
          expr: unquote(expr),
          message: unquote(message <> ", both sides are exactly equal")
      else
        assert unquote(call),
          left: left,
          right: right,
          expr: unquote(expr),
          message: unquote(message)
      end
    end
  end

  defp translate_assertion(kind, {_, _, [left, right]} = expr, call, message, false, _caller) do
    expr = escape_quoted(kind, expr)

    quote do
      left = unquote(left)
      right = unquote(right)

      assert unquote(call),
        left: left,
        right: right,
        expr: unquote(expr),
        message: unquote(message)
    end
  end

  @doc false
  def __equal__?(left, right) do
    left === right
  end

  defp escape_quoted(kind, expr) do
    Macro.escape({kind, [], [expr]})
  end

  ## END HELPERS

  @doc """
  Asserts `value` is `true`, displaying the given `message` otherwise.

  ## Examples

      assert false, "it will never be true"

  """
  def assert(value, message) when is_binary(message) do
    assert(value, message: message)
  end

  def assert(value, opts) when is_list(opts) do
    unless value, do: raise(ExUnit.AssertionError, opts)
    true
  end

  @doc """
  Asserts that a message matching `pattern` was or is going to be received
  within the `timeout` period, specified in milliseconds.

  Unlike `assert_received`, it has a default `timeout`
  of 100 milliseconds.

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` is not received.

  ## Examples

      assert_receive :hello

  Asserts against a larger timeout:

      assert_receive :hello, 20_000

  You can also match against specific patterns:

      assert_receive {:hello, _}

      x = 5
      assert_receive {:count, ^x}

  """
  defmacro assert_receive(
             pattern,
             timeout \\ Application.fetch_env!(:ex_unit, :assert_receive_timeout),
             failure_message \\ nil
           ) do
    assert_receive(pattern, timeout, failure_message, __CALLER__)
  end

  @doc """
  Asserts that a message matching `pattern` was received and is in the
  current process' mailbox.

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` was not received.

  Timeout is set to 0, so there is no waiting time.

  ## Examples

      send self(), :hello
      assert_received :hello

      send self(), :bye
      assert_received :hello, "Oh No!"
      ** (ExUnit.AssertionError) Oh No!
      Process mailbox:
        :bye

  You can also match against specific patterns:

      send self(), {:hello, "world"}
      assert_received {:hello, _}

  """
  defmacro assert_received(pattern, failure_message \\ nil) do
    assert_receive(pattern, 0, failure_message, __CALLER__)
  end

  defp assert_receive(pattern, timeout, failure_message, caller) do
    binary = Macro.to_string(pattern)

    # Expand before extracting metadata
    caller = Macro.Env.to_match(caller)
    expanded = expand_pattern(pattern, caller)
    vars = collect_vars_from_pattern(expanded)
    pins = collect_pins_from_pattern(expanded, caller.vars)

    pattern =
      case pattern do
        {:when, meta, [left, right]} ->
          {:when, meta, [quote(do: unquote(left) = received), right]}

        left ->
          quote(do: unquote(left) = received)
      end

    quoted_pattern =
      quote do
        case message do
          unquote(pattern) ->
            _ = unquote(vars)
            true

          _ ->
            false
        end
      end

    pattern_finder =
      quote do
        fn message ->
          unquote(suppress_warning(quoted_pattern))
        end
      end

    failure_message_hit =
      failure_message ||
        quote do
          """
          Found message matching #{unquote(binary)} after #{timeout}ms.

          This means the message was delivered too close to the timeout value, you may want to either:

            1. Give an increased timeout to `assert_receive/2`
            2. Increase the default timeout to all `assert_receive` in your
               test_helper.exs by setting ExUnit.configure(assert_receive_timeout: ...)
          """
        end

    failure_message_miss =
      failure_message ||
        quote do
          "No message matching #{unquote(binary)} after #{timeout}ms."
        end

    quote do
      timeout = unquote(timeout)

      {received, unquote(vars)} =
        receive do
          unquote(pattern) ->
            {received, unquote(vars)}
        after
          timeout ->
            {:messages, messages} = Process.info(self(), :messages)

            if Enum.any?(messages, unquote(pattern_finder)) do
              flunk(unquote(failure_message_hit))
            else
              flunk(
                unquote(failure_message_miss) <>
                  ExUnit.Assertions.__pins__(unquote(pins)) <>
                  ExUnit.Assertions.__mailbox__(messages)
              )
            end
        end

      received
    end
  end

  @indent "\n  "
  @max_mailbox_length 10

  @doc false
  def __mailbox__(messages) do
    length = length(messages)

    mailbox =
      messages
      |> Enum.take(@max_mailbox_length)
      |> Enum.map_join(@indent, &inspect/1)

    mailbox_message(length, @indent <> mailbox)
  end

  @doc false
  def __pins__([]), do: ""

  def __pins__(pins) do
    content =
      pins
      |> Enum.reverse()
      |> Enum.map_join(@indent, fn {name, var} -> "#{name} = #{inspect(var)}" end)

    "\nThe following variables were pinned:" <> @indent <> content
  end

  defp mailbox_message(0, _mailbox), do: "\nThe process mailbox is empty."

  defp mailbox_message(length, mailbox) when length > 10 do
    "\nProcess mailbox:" <>
      mailbox <> "\nShowing only #{@max_mailbox_length} of #{length} messages."
  end

  defp mailbox_message(_length, mailbox) do
    "\nProcess mailbox:" <> mailbox
  end

  defp collect_pins_from_pattern(expr, vars) do
    {_, pins} =
      Macro.prewalk(expr, [], fn
        {:^, _, [{name, _, nil} = var]}, acc ->
          if {name, nil} in vars do
            {:ok, [{name, var} | acc]}
          else
            {:ok, acc}
          end

        form, acc ->
          {form, acc}
      end)

    Enum.uniq_by(pins, &elem(&1, 0))
  end

  defp collect_vars_from_pattern({:when, _, [left, right]}) do
    pattern = collect_vars_from_pattern(left)

    for {name, _, context} = var <- collect_vars_from_pattern(right),
        Enum.any?(pattern, &match?({^name, _, ^context}, &1)),
        into: pattern,
        do: var
  end

  defp collect_vars_from_pattern(expr) do
    Macro.prewalk(expr, [], fn
      {:::, _, [left, _]}, acc ->
        {[left], acc}

      {skip, _, [_]}, acc when skip in [:^, :@] ->
        {:ok, acc}

      {:_, _, context}, acc when is_atom(context) ->
        {:ok, acc}

      {name, meta, context}, acc when is_atom(name) and is_atom(context) ->
        {:ok, [{name, [generated: true] ++ meta, context} | acc]}

      node, acc ->
        {node, acc}
    end)
    |> elem(1)
  end

  defp expand_pattern(expr, caller) do
    Macro.prewalk(expr, fn
      {var, _, context} = node when is_atom(var) and is_atom(context) ->
        node

      other ->
        Macro.expand(other, caller)
    end)
  end

  defp suppress_warning({name, meta, [expr, [do: clauses]]}) do
    clauses =
      Enum.map(clauses, fn {:->, meta, args} ->
        {:->, [generated: true] ++ meta, args}
      end)

    {name, meta, [expr, [do: clauses]]}
  end

  @doc """
  Asserts the `exception` is raised during `function` execution with
  the expected `message`, which can be a `Regex` or an exact `String`.
  Returns the rescued exception, fails otherwise.

  ## Examples

      assert_raise ArithmeticError, "bad argument in arithmetic expression", fn ->
        1 + "test"
      end

      assert_raise RuntimeError, ~r/^today's lucky number is 0\.\d+!$/, fn ->
        raise "today's lucky number is #{:rand.uniform()}!"
      end
  """
  def assert_raise(exception, message, function) when is_function(function) do
    error = assert_raise(exception, function)

    match? =
      cond do
        is_binary(message) -> Exception.message(error) == message
        Regex.regex?(message) -> Exception.message(error) =~ message
      end

    message =
      "Wrong message for #{inspect(exception)}\n" <>
        "expected:\n  #{inspect(message)}\n" <>
        "actual:\n" <> "  #{inspect(Exception.message(error))}"

    assert match?, message: message

    error
  end

  @doc """
  Asserts the `exception` is raised during `function` execution.
  Returns the rescued exception, fails otherwise.

  ## Examples

      assert_raise ArithmeticError, fn ->
        1 + "test"
      end

  """
  def assert_raise(exception, function) when is_function(function) do
    try do
      function.()
    rescue
      error ->
        stacktrace = System.stacktrace()
        name = error.__struct__

        cond do
          name == exception ->
            check_error_message(name, error)
            error

          name == ExUnit.AssertionError ->
            reraise(error, stacktrace)

          true ->
            message =
              "Expected exception #{inspect(exception)} " <>
                "but got #{inspect(name)} (#{Exception.message(error)})"

            reraise ExUnit.AssertionError, [message: message], stacktrace
        end
    else
      _ -> flunk("Expected exception #{inspect(exception)} but nothing was raised")
    end
  end

  defp check_error_message(module, error) do
    module.message(error)
  catch
    kind, reason ->
      stacktrace = System.stacktrace()

      message =
        "Got exception #{inspect(module)} but it failed to produce a message with:\n\n" <>
          Exception.format(kind, reason, stacktrace)

      flunk(message)
  end

  @doc """
  Asserts that `value1` and `value2` differ by no more than `delta`.

  This difference is inclusive, so the test will pass if the difference
  and the `delta` are equal.

  ## Examples

      assert_in_delta 1.1, 1.5, 0.2
      assert_in_delta 10, 15, 2
      assert_in_delta 10, 15, 5

  """
  def assert_in_delta(value1, value2, delta, message \\ nil)

  def assert_in_delta(_, _, delta, _) when delta < 0 do
    raise ArgumentError, "delta must always be a positive number, got: #{inspect(delta)}"
  end

  def assert_in_delta(value1, value2, delta, message) do
    diff = abs(value1 - value2)

    message =
      message ||
        "Expected the difference between #{inspect(value1)} and " <>
          "#{inspect(value2)} (#{inspect(diff)}) to be less than or equal to #{inspect(delta)}"

    assert diff <= delta, message
  end

  @doc """
  Asserts `expression` will throw a value.
  Returns the thrown value or fails otherwise.

  ## Examples

      assert catch_throw(throw 1) == 1

  """
  defmacro catch_throw(expression) do
    do_catch(:throw, expression)
  end

  @doc """
  Asserts `expression` will exit.
  Returns the exit status/message or fails otherwise.

  ## Examples

      assert catch_exit(exit 1) == 1

  """
  defmacro catch_exit(expression) do
    do_catch(:exit, expression)
  end

  @doc """
  Asserts `expression` will cause an error.
  Returns the error or fails otherwise.

  ## Examples

      assert catch_error(error 1) == 1

  """
  defmacro catch_error(expression) do
    do_catch(:error, expression)
  end

  defp do_catch(kind, expr) do
    quote do
      try do
        _ = unquote(expr)
        flunk("Expected to catch #{unquote(kind)}, got nothing")
      rescue
        e in [ExUnit.AssertionError] ->
          reraise(e, System.stacktrace())
      catch
        unquote(kind), we_got -> we_got
      end
    end
  end

  @doc """
  Asserts `value` is `nil` or `false` (that is, `value` is not truthy).

  ## Examples

      refute true, "This will obviously fail"

  """
  def refute(value, message) do
    not assert(!value, message)
  end

  @doc """
  Asserts that a message matching `pattern` was not received (and won't be received)
  within the `timeout` period, specified in milliseconds.

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` is received.

  ## Examples

      refute_receive :bye

  Refute received with an explicit timeout:

      refute_receive :bye, 1000

  """
  defmacro refute_receive(
             pattern,
             timeout \\ Application.fetch_env!(:ex_unit, :refute_receive_timeout),
             failure_message \\ nil
           ) do
    do_refute_receive(pattern, timeout, failure_message)
  end

  @doc """
  Asserts a message matching `pattern` was not received (i.e. it is not in the
  current process' mailbox).

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` was received.

  Timeout is set to 0, so there is no waiting time.

  ## Examples

      send self(), :hello
      refute_received :bye

      send self(), :hello
      refute_received :hello, "Oh No!"
      ** (ExUnit.AssertionError) Oh No!
      Process mailbox:
        :bye

  """
  defmacro refute_received(pattern, failure_message \\ nil) do
    do_refute_receive(pattern, 0, failure_message)
  end

  defp do_refute_receive(pattern, timeout, failure_message) do
    receive_clause = refute_receive_clause(pattern, failure_message)

    quote do
      receive do
        unquote(receive_clause)
      after
        unquote(timeout) -> false
      end
    end
  end

  defp refute_receive_clause(pattern, nil) do
    binary = Macro.to_string(pattern)

    quote do
      unquote(pattern) = actual ->
        flunk(
          "Unexpectedly received message #{inspect(actual)} (which matched #{unquote(binary)})"
        )
    end
  end

  defp refute_receive_clause(pattern, failure_message) do
    quote do
      unquote(pattern) -> flunk(unquote(failure_message))
    end
  end

  @doc """
  Asserts `value1` and `value2` are not within `delta`.

  This difference is exclusive, so the test will fail if the difference
  and the delta are equal.

  If you supply `message`, information about the values will
  automatically be appended to it.

  ## Examples

      refute_in_delta 1.1, 1.2, 0.2
      refute_in_delta 10, 11, 2

  """
  def refute_in_delta(value1, value2, delta, message \\ nil) do
    diff = abs(value1 - value2)

    message =
      if message do
        message <>
          " (difference between #{inspect(value1)} " <>
          "and #{inspect(value2)} is less than #{inspect(delta)})"
      else
        "Expected the difference between #{inspect(value1)} and " <>
          "#{inspect(value2)} (#{inspect(diff)}) to be more than #{inspect(delta)}"
      end

    refute diff < delta, message
  end

  @doc """
  Fails with a message.

  ## Examples

      flunk "This should raise an error"

  """
  @spec flunk :: no_return
  @spec flunk(String.t()) :: no_return
  def flunk(message \\ "Flunked!") when is_binary(message) do
    assert false, message: message
  end
end

defmodule ExUnit.AssertionError do
  @moduledoc """
  Raised to signal an assertion error.
  """

  @no_value :ex_unit_no_meaningful_value

  defexception left: @no_value, right: @no_value, message: @no_value, expr: @no_value

  @doc """
  Indicates no meaningful value for a field.
  """
  def no_value do
    @no_value
  end

  def message(exception) do
    "\n\n" <> ExUnit.Formatter.format_assertion_error(exception)
  end
end

defmodule ExUnit.MultiError do
  @moduledoc """
  Raised to signal multiple errors happened in a test case.
  """

  defexception errors: []

  def message(%{errors: errors}) do
    "got the following errors:\n\n" <>
      Enum.map_join(errors, "\n\n", fn {kind, error, stack} ->
        Exception.format_banner(kind, error, stack)
      end)
  end
end

defmodule ExUnit.Assertions do
  @moduledoc """
  This module contains a set of assertion functions that are
  imported by default into your test cases.

  In general, a developer will want to use the general
  `assert` macro in tests. This macro introspects your code
  and provides good reporting whenever there is a failure.
  For example, `assert some_fun() == 10` will fail (assuming
  `some_fun()` returns `13`):

      Comparison (using ==) failed in:
      code:  some_fun() == 10
      left:  13
      right: 10

  This module also provides other convenience functions
  like `assert_in_delta` and `assert_raise` to easily handle
  other common cases such as checking a floating-point number
  or handling exceptions.
  """

  @doc """
  Asserts its argument is a truthy value.

  `assert` introspects the underlying expression and provides
  good reporting whenever there is a failure. For example,
  if the expression uses the comparison operator, the message
  will show the values of the two sides. The assertion

      assert 1 + 2 + 3 + 4 > 15

   will fail with the message:

      Assertion with > failed
      code:  1 + 2 + 3 + 4 > 15
      left:  10
      right: 15

  Similarly, if a match expression is given, it will report
  any failure in terms of that match. Given

      assert [1] = [2]

  you'll see:

      match (=) failed
      code:  [1] = [2]
      right: [2]

  Keep in mind that `assert` does not change its semantics
  based on the expression. In other words, the expression
  is still required to return a truthy value. For example,
  the following will fail:

      assert nil = some_function_that_returns_nil()

  Even though the match works, `assert` still expects a truth
  value. In such cases, simply use `Kernel.==/2` or
  `Kernel.match?/2`.
  """
  defmacro assert({:=, _, [left, right]} = assertion) do
    code = escape_quoted(:assert, assertion)

    left = expand_pattern(left, __CALLER__)
    vars = collect_vars_from_pattern(left)
    pins = collect_pins_from_pattern(left, __CALLER__.vars)

    # If the match works, we need to check if the value
    # is not nil nor false. We need to rewrite the if
    # to avoid silly warnings though.
    return =
      suppress_warning(
        quote do
          case right do
            x when x in [nil, false] ->
              raise ExUnit.AssertionError,
                expr: expr,
                message: "Expected truthy, got #{inspect(right)}"

            _ ->
              :ok
          end
        end
      )

    match_expr =
      suppress_warning(
        quote do
          case right do
            unquote(left) ->
              unquote(return)
              unquote(vars)

            _ ->
              raise ExUnit.AssertionError,
                right: right,
                expr: expr,
                message: "match (=) failed" <> ExUnit.Assertions.__pins__(unquote(pins))
          end
        end
      )

    quote do
      right = unquote(right)
      expr = unquote(code)
      unquote(vars) = unquote(match_expr)
      right
    end
  end

  defmacro assert({:match?, meta, [left, right]} = assertion) do
    code = escape_quoted(:assert, assertion)
    match? = {:match?, meta, [left, Macro.var(:right, __MODULE__)]}
    pins = collect_pins_from_pattern(left, __CALLER__.vars)

    quote do
      right = unquote(right)

      assert unquote(match?),
        right: right,
        expr: unquote(code),
        message: "match (match?) failed" <> ExUnit.Assertions.__pins__(unquote(pins))
    end
  end

  defmacro assert(assertion) do
    case translate_assertion(:assert, assertion, __CALLER__) do
      nil ->
        quote do
          value = unquote(assertion)

          unless value do
            raise ExUnit.AssertionError,
              expr: unquote(escape_quoted(:assert, assertion)),
              message: "Expected truthy, got #{inspect(value)}"
          end

          value
        end

      value ->
        value
    end
  end

  @doc """
  A negative assertion, expects the expression to be `false` or `nil`.

  Keep in mind that `refute` does not change the semantics of
  the given expression. In other words, the following will fail:

      refute {:ok, _} = some_function_that_returns_error_tuple()

  The code above will fail because the `=` operator always fails
  when the sides do not match and `refute/2` does not change it.

  The correct way to write the refutation above is to use
  `Kernel.match?/2`:

      refute match? {:ok, _}, some_function_that_returns_error_tuple()

  ## Examples

      refute age < 0

  """
  defmacro refute({:match?, meta, [left, right]} = assertion) do
    code = escape_quoted(:refute, assertion)
    match? = {:match?, meta, [left, Macro.var(:right, __MODULE__)]}
    pins = collect_pins_from_pattern(left, __CALLER__.vars)

    quote do
      right = unquote(right)

      refute unquote(match?),
        right: right,
        expr: unquote(code),
        message:
          "match (match?) succeeded, but should have failed" <>
            ExUnit.Assertions.__pins__(unquote(pins))
    end
  end

  defmacro refute(assertion) do
    case translate_assertion(:refute, assertion, __CALLER__) do
      nil ->
        quote do
          value = unquote(assertion)

          if value do
            raise ExUnit.AssertionError,
              expr: unquote(escape_quoted(:refute, assertion)),
              message: "Expected false or nil, got #{inspect(value)}"
          end

          value
        end

      value ->
        {:!, [], [value]}
    end
  end

  ## START HELPERS

  @operator [:==, :<, :>, :<=, :>=, :===, :=~, :!==, :!=, :in]

  defp translate_assertion(:assert, {operator, meta, [_, _]} = expr, caller)
       when operator in @operator do
    left = Macro.var(:left, __MODULE__)
    right = Macro.var(:right, __MODULE__)
    call = {operator, meta, [left, right]}
    equality_check? = operator in [:<, :>, :!==, :!=]
    message = "Assertion with #{operator} failed"
    translate_assertion(:assert, expr, call, message, equality_check?, caller)
  end

  defp translate_assertion(:refute, {operator, meta, [_, _]} = expr, caller)
       when operator in @operator do
    left = Macro.var(:left, __MODULE__)
    right = Macro.var(:right, __MODULE__)
    call = {:not, meta, [{operator, meta, [left, right]}]}
    equality_check? = operator in [:<=, :>=, :===, :==, :=~]
    message = "Refute with #{operator} failed"
    translate_assertion(:refute, expr, call, message, equality_check?, caller)
  end

  defp translate_assertion(_kind, _expected, _caller) do
    nil
  end

  defp translate_assertion(kind, {_, _, [left, right]} = expr, call, message, true, _caller) do
    expr = escape_quoted(kind, expr)

    quote do
      left = unquote(left)
      right = unquote(right)

      if ExUnit.Assertions.__equal__?(left, right) do
        assert false,
          left: left,
          expr: unquote(expr),
          message: unquote(message <> ", both sides are exactly equal")
      else
        assert unquote(call),
          left: left,
          right: right,
          expr: unquote(expr),
          message: unquote(message)
      end
    end
  end

  defp translate_assertion(kind, {_, _, [left, right]} = expr, call, message, false, _caller) do
    expr = escape_quoted(kind, expr)

    quote do
      left = unquote(left)
      right = unquote(right)

      assert unquote(call),
        left: left,
        right: right,
        expr: unquote(expr),
        message: unquote(message)
    end
  end

  @doc false
  def __equal__?(left, right) do
    left === right
  end

  defp escape_quoted(kind, expr) do
    Macro.escape({kind, [], [expr]})
  end

  ## END HELPERS

  @doc """
  Asserts `value` is `true`, displaying the given `message` otherwise.

  ## Examples

      assert false, "it will never be true"

  """
  def assert(value, message) when is_binary(message) do
    assert(value, message: message)
  end

  def assert(value, opts) when is_list(opts) do
    unless value, do: raise(ExUnit.AssertionError, opts)
    true
  end

  @doc """
  Asserts that a message matching `pattern` was or is going to be received
  within the `timeout` period, specified in milliseconds.

  Unlike `assert_received`, it has a default `timeout`
  of 100 milliseconds.

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` is not received.

  ## Examples

      assert_receive :hello

  Asserts against a larger timeout:

      assert_receive :hello, 20_000

  You can also match against specific patterns:

      assert_receive {:hello, _}

      x = 5
      assert_receive {:count, ^x}

  """
  defmacro assert_receive(
             pattern,
             timeout \\ Application.fetch_env!(:ex_unit, :assert_receive_timeout),
             failure_message \\ nil
           ) do
    assert_receive(pattern, timeout, failure_message, __CALLER__)
  end

  @doc """
  Asserts that a message matching `pattern` was received and is in the
  current process' mailbox.

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` was not received.

  Timeout is set to 0, so there is no waiting time.

  ## Examples

      send self(), :hello
      assert_received :hello

      send self(), :bye
      assert_received :hello, "Oh No!"
      ** (ExUnit.AssertionError) Oh No!
      Process mailbox:
        :bye

  You can also match against specific patterns:

      send self(), {:hello, "world"}
      assert_received {:hello, _}

  """
  defmacro assert_received(pattern, failure_message \\ nil) do
    assert_receive(pattern, 0, failure_message, __CALLER__)
  end

  defp assert_receive(pattern, timeout, failure_message, caller) do
    binary = Macro.to_string(pattern)

    # Expand before extracting metadata
    caller = Macro.Env.to_match(caller)
    expanded = expand_pattern(pattern, caller)
    vars = collect_vars_from_pattern(expanded)
    pins = collect_pins_from_pattern(expanded, caller.vars)

    pattern =
      case pattern do
        {:when, meta, [left, right]} ->
          {:when, meta, [quote(do: unquote(left) = received), right]}

        left ->
          quote(do: unquote(left) = received)
      end

    quoted_pattern =
      quote do
        case message do
          unquote(pattern) ->
            _ = unquote(vars)
            true

          _ ->
            false
        end
      end

    pattern_finder =
      quote do
        fn message ->
          unquote(suppress_warning(quoted_pattern))
        end
      end

    failure_message_hit =
      failure_message ||
        quote do
          """
          Found message matching #{unquote(binary)} after #{timeout}ms.

          This means the message was delivered too close to the timeout value, you may want to either:

            1. Give an increased timeout to `assert_receive/2`
            2. Increase the default timeout to all `assert_receive` in your
               test_helper.exs by setting ExUnit.configure(assert_receive_timeout: ...)
          """
        end

    failure_message_miss =
      failure_message ||
        quote do
          "No message matching #{unquote(binary)} after #{timeout}ms."
        end

    quote do
      timeout = unquote(timeout)

      {received, unquote(vars)} =
        receive do
          unquote(pattern) ->
            {received, unquote(vars)}
        after
          timeout ->
            {:messages, messages} = Process.info(self(), :messages)

            if Enum.any?(messages, unquote(pattern_finder)) do
              flunk(unquote(failure_message_hit))
            else
              flunk(
                unquote(failure_message_miss) <>
                  ExUnit.Assertions.__pins__(unquote(pins)) <>
                  ExUnit.Assertions.__mailbox__(messages)
              )
            end
        end

      received
    end
  end

  @indent "\n  "
  @max_mailbox_length 10

  @doc false
  def __mailbox__(messages) do
    length = length(messages)

    mailbox =
      messages
      |> Enum.take(@max_mailbox_length)
      |> Enum.map_join(@indent, &inspect/1)

    mailbox_message(length, @indent <> mailbox)
  end

  @doc false
  def __pins__([]), do: ""

  def __pins__(pins) do
    content =
      pins
      |> Enum.reverse()
      |> Enum.map_join(@indent, fn {name, var} -> "#{name} = #{inspect(var)}" end)

    "\nThe following variables were pinned:" <> @indent <> content
  end

  defp mailbox_message(0, _mailbox), do: "\nThe process mailbox is empty."

  defp mailbox_message(length, mailbox) when length > 10 do
    "\nProcess mailbox:" <>
      mailbox <> "\nShowing only #{@max_mailbox_length} of #{length} messages."
  end

  defp mailbox_message(_length, mailbox) do
    "\nProcess mailbox:" <> mailbox
  end

  defp collect_pins_from_pattern(expr, vars) do
    {_, pins} =
      Macro.prewalk(expr, [], fn
        {:^, _, [{name, _, nil} = var]}, acc ->
          if {name, nil} in vars do
            {:ok, [{name, var} | acc]}
          else
            {:ok, acc}
          end

        form, acc ->
          {form, acc}
      end)

    Enum.uniq_by(pins, &elem(&1, 0))
  end

  defp collect_vars_from_pattern({:when, _, [left, right]}) do
    pattern = collect_vars_from_pattern(left)

    for {name, _, context} = var <- collect_vars_from_pattern(right),
        Enum.any?(pattern, &match?({^name, _, ^context}, &1)),
        into: pattern,
        do: var
  end

  defp collect_vars_from_pattern(expr) do
    Macro.prewalk(expr, [], fn
      {:::, _, [left, _]}, acc ->
        {[left], acc}

      {skip, _, [_]}, acc when skip in [:^, :@] ->
        {:ok, acc}

      {:_, _, context}, acc when is_atom(context) ->
        {:ok, acc}

      {name, meta, context}, acc when is_atom(name) and is_atom(context) ->
        {:ok, [{name, [generated: true] ++ meta, context} | acc]}

      node, acc ->
        {node, acc}
    end)
    |> elem(1)
  end

  defp expand_pattern(expr, caller) do
    Macro.prewalk(expr, fn
      {var, _, context} = node when is_atom(var) and is_atom(context) ->
        node

      other ->
        Macro.expand(other, caller)
    end)
  end

  defp suppress_warning({name, meta, [expr, [do: clauses]]}) do
    clauses =
      Enum.map(clauses, fn {:->, meta, args} ->
        {:->, [generated: true] ++ meta, args}
      end)

    {name, meta, [expr, [do: clauses]]}
  end

  @doc """
  Asserts the `exception` is raised during `function` execution with
  the expected `message`, which can be a `Regex` or an exact `String`.
  Returns the rescued exception, fails otherwise.

  ## Examples

      assert_raise ArithmeticError, "bad argument in arithmetic expression", fn ->
        1 + "test"
      end

      assert_raise RuntimeError, ~r/^today's lucky number is 0\.\d+!$/, fn ->
        raise "today's lucky number is #{:rand.uniform()}!"
      end
  """
  def assert_raise(exception, message, function) when is_function(function) do
    error = assert_raise(exception, function)

    match? =
      cond do
        is_binary(message) -> Exception.message(error) == message
        Regex.regex?(message) -> Exception.message(error) =~ message
      end

    message =
      "Wrong message for #{inspect(exception)}\n" <>
        "expected:\n  #{inspect(message)}\n" <>
        "actual:\n" <> "  #{inspect(Exception.message(error))}"

    assert match?, message: message

    error
  end

  @doc """
  Asserts the `exception` is raised during `function` execution.
  Returns the rescued exception, fails otherwise.

  ## Examples

      assert_raise ArithmeticError, fn ->
        1 + "test"
      end

  """
  def assert_raise(exception, function) when is_function(function) do
    try do
      function.()
    rescue
      error ->
        stacktrace = System.stacktrace()
        name = error.__struct__

        cond do
          name == exception ->
            check_error_message(name, error)
            error

          name == ExUnit.AssertionError ->
            reraise(error, stacktrace)

          true ->
            message =
              "Expected exception #{inspect(exception)} " <>
                "but got #{inspect(name)} (#{Exception.message(error)})"

            reraise ExUnit.AssertionError, [message: message], stacktrace
        end
    else
      _ -> flunk("Expected exception #{inspect(exception)} but nothing was raised")
    end
  end

  defp check_error_message(module, error) do
    module.message(error)
  catch
    kind, reason ->
      stacktrace = System.stacktrace()

      message =
        "Got exception #{inspect(module)} but it failed to produce a message with:\n\n" <>
          Exception.format(kind, reason, stacktrace)

      flunk(message)
  end

  @doc """
  Asserts that `value1` and `value2` differ by no more than `delta`.

  ## Examples

      assert_in_delta 1.1, 1.5, 0.2
      assert_in_delta 10, 15, 4

  """
  def assert_in_delta(value1, value2, delta, message \\ nil)

  def assert_in_delta(_, _, delta, _) when delta < 0 do
    raise ArgumentError, "delta must always be a positive number, got: #{inspect(delta)}"
  end

  def assert_in_delta(value1, value2, delta, message) do
    diff = abs(value1 - value2)

    message =
      message ||
        "Expected the difference between #{inspect(value1)} and " <>
          "#{inspect(value2)} (#{inspect(diff)}) to be less than or equal to #{inspect(delta)}"

    assert diff <= delta, message
  end

  @doc """
  Asserts `expression` will throw a value.
  Returns the thrown value or fails otherwise.

  ## Examples

      assert catch_throw(throw 1) == 1

  """
  defmacro catch_throw(expression) do
    do_catch(:throw, expression)
  end

  @doc """
  Asserts `expression` will exit.
  Returns the exit status/message or fails otherwise.

  ## Examples

      assert catch_exit(exit 1) == 1

  """
  defmacro catch_exit(expression) do
    do_catch(:exit, expression)
  end

  @doc """
  Asserts `expression` will cause an error.
  Returns the error or fails otherwise.

  ## Examples

      assert catch_error(error 1) == 1

  """
  defmacro catch_error(expression) do
    do_catch(:error, expression)
  end

  defp do_catch(kind, expr) do
    quote do
      try do
        _ = unquote(expr)
        flunk("Expected to catch #{unquote(kind)}, got nothing")
      rescue
        e in [ExUnit.AssertionError] ->
          reraise(e, System.stacktrace())
      catch
        unquote(kind), we_got -> we_got
      end
    end
  end

  @doc """
  Asserts `value` is `nil` or `false` (that is, `value` is not truthy).

  ## Examples

      refute true, "This will obviously fail"

  """
  def refute(value, message) do
    not assert(!value, message)
  end

  @doc """
  Asserts that a message matching `pattern` was not received (and won't be received)
  within the `timeout` period, specified in milliseconds.

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` is received.

  ## Examples

      refute_receive :bye

  Refute received with an explicit timeout:

      refute_receive :bye, 1000

  """
  defmacro refute_receive(
             pattern,
             timeout \\ Application.fetch_env!(:ex_unit, :refute_receive_timeout),
             failure_message \\ nil
           ) do
    do_refute_receive(pattern, timeout, failure_message)
  end

  @doc """
  Asserts a message matching `pattern` was not received (i.e. it is not in the
  current process' mailbox).

  The `pattern` argument must be a match pattern. Flunks with `failure_message`
  if a message matching `pattern` was received.

  Timeout is set to 0, so there is no waiting time.

  ## Examples

      send self(), :hello
      refute_received :bye

      send self(), :hello
      refute_received :hello, "Oh No!"
      ** (ExUnit.AssertionError) Oh No!
      Process mailbox:
        :bye

  """
  defmacro refute_received(pattern, failure_message \\ nil) do
    do_refute_receive(pattern, 0, failure_message)
  end

  defp do_refute_receive(pattern, timeout, failure_message) do
    receive_clause = refute_receive_clause(pattern, failure_message)

    quote do
      receive do
        unquote(receive_clause)
      after
        unquote(timeout) -> false
      end
    end
  end

  defp refute_receive_clause(pattern, nil) do
    binary = Macro.to_string(pattern)

    quote do
      unquote(pattern) = actual ->
        flunk(
          "Unexpectedly received message #{inspect(actual)} (which matched #{unquote(binary)})"
        )
    end
  end

  defp refute_receive_clause(pattern, failure_message) do
    quote do
      unquote(pattern) -> flunk(unquote(failure_message))
    end
  end

  @doc """
  Asserts `value1` and `value2` are not within `delta`.

  If you supply `message`, information about the values will
  automatically be appended to it.

  ## Examples

      refute_in_delta 1.1, 1.2, 0.2
      refute_in_delta 10, 11, 2

  """
  def refute_in_delta(value1, value2, delta, message \\ nil) do
    diff = abs(value1 - value2)

    message =
      if message do
        message <>
          " (difference between #{inspect(value1)} " <>
          "and #{inspect(value2)} is less than #{inspect(delta)})"
      else
        "Expected the difference between #{inspect(value1)} and " <>
          "#{inspect(value2)} (#{inspect(diff)}) to be more than #{inspect(delta)}"
      end

    refute diff < delta, message
  end

  @doc """
  Fails with a message.

  ## Examples

      flunk "This should raise an error"

  """
  @spec flunk :: no_return
  @spec flunk(String.t()) :: no_return
  def flunk(message \\ "Flunked!") when is_binary(message) do
    assert false, message: message
  end
end