   end
 
   setup do
-    on_exit fn -> purge(Kernel.ImplTest.ImplAttributes) end
+    on_exit(fn -> purge(Kernel.ImplTest.ImplAttributes) end)
   end
 
   defmodule Behaviour do


  test "warns for undefined value" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour :abc

        @impl :abc
        def foo(), do: :ok
      end
      """
    end) =~ "got \"@impl :abc\" for function foo/0 but this behaviour does not specify such callback. There are no known callbacks"
  end

  test "warns for callbacks without impl and @impl has been set before" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.MacroBehaviour

        @impl true
        def foo(), do: :ok

        defmacro bar(), do: :ok
      end
      """
    end) =~ "module attribute @impl was not set for macro bar/0 callback (specified in Kernel.ImplTest.MacroBehaviour)"
  end

  test "warns for callbacks without impl and @impl has been set after" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.MacroBehaviour

        defmacro bar(), do: :ok

        @impl true
        def foo(), do: :ok
      end
      """
    end) =~ "module attribute @impl was not set for macro bar/0 callback (specified in Kernel.ImplTest.MacroBehaviour)"
  end

  test "warns when @impl is set on private function" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl true
        defp foo(), do: :ok
      end
      """
    end) =~ "function foo/0 is private, @impl attribute is always discarded for private functions/macros"
  end

  test "warns when @impl is set and no function" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl true
      end
      """
    end) =~ "module attribute @impl was set but no definition follows it"
  end

  test "warns for @impl true and no behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @impl true
        def foo(), do: :ok
      end
      """
    end) =~ "got \"@impl true\" for function foo/0 but no behaviour was declared"
  end

  test "warns for @impl true with callback name not in behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl true
        def bar(), do: :ok
      end
      """
    end) =~ "got \"@impl true\" for function bar/0 but no behaviour specifies such callback"
  end

  test "warns for @impl true with macro callback name not in behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.MacroBehaviour
        @impl true
        defmacro foo(), do: :ok
      end
      """
    end) =~ "got \"@impl true\" for macro foo/0 but no behaviour specifies such callback"
  end

  test "warns for @impl true with callback kind not in behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.MacroBehaviour
        @impl true
        def foo(), do: :ok
      end
      """
    end) =~ "got \"@impl true\" for function foo/0 but no behaviour specifies such callback"
  end

  test "warns for @impl true with wrong arity" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl true
        def foo(arg), do: arg
      end
      """
    end) =~ "got \"@impl true\" for function foo/1 but no behaviour specifies such callback"
  end

  test "warns for @impl false and there are no callbacks" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @impl false
        def baz(term), do: term
      end
      """
    end) =~ "got \"@impl false\" for function baz/1 but no behaviour was declared"
  end

  test "warns for @impl false and it is a callback" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl false
        def foo(), do: :ok
      end
      """
    end) =~ "got \"@impl false\" for function foo/0 but it is a callback specified in Kernel.ImplTest.Behaviour"
  end

  test "warns for @impl module and no behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @impl Kernel.ImplTest.Behaviour
        def foo(), do: :ok
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.Behaviour\" for function foo/0 but no behaviour was declared"
  end

  test "warns for @impl module with callback name not in behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl Kernel.ImplTest.Behaviour
        def bar(), do: :ok
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but this behaviour does not specify such callback"
  end

  test "warns for @impl module with macro callback name not in behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.MacroBehaviour
        @impl Kernel.ImplTest.MacroBehaviour
        defmacro foo(), do: :ok
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.MacroBehaviour\" for macro foo/0 but this behaviour does not specify such callback"
  end

  test "warns for @impl module with macro callback kind not in behaviour" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.MacroBehaviour
        @impl Kernel.ImplTest.MacroBehaviour
        def foo(), do: :ok
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but this behaviour does not specify such callback"
  end

  test "warns for @impl module and callback belongs to another known module" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.MacroBehaviour
        @impl Kernel.ImplTest.Behaviour
        def bar(), do: :ok
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but this behaviour does not specify such callback"
  end

  test "warns for @impl module and callback belongs to another unknown module" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @impl Kernel.ImplTest.MacroBehaviour
        def bar(), do: :ok
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function bar/0 but this behaviour was not declared with @behaviour"
  end

  test "does not warn for @impl when using default arguments" do
    assert capture_err(fn ->
      Code.eval_string ~S"""
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.BehaviourWithArgument

        @impl true
        def foo(args \\ []), do: args
      end
      """
    end) == ""
  end

  test "does not warn for no @impl when overriding callback" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour

        def foo(), do: :overridable

        defoverridable Kernel.ImplTest.Behaviour

        def foo(), do: :overridden
      end
      """
    end) == ""
  end

  test "does not warn for overridable function missing @impl" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour

        def foo(), do: :overridable

        defoverridable Kernel.ImplTest.Behaviour

        @impl Kernel.ImplTest.Behaviour
        def foo(), do: :overridden
      end
      """
    end) == ""
  end

  test "warns correctly for missing @impl only for end-user implemented function" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.MacroBehaviour

        def foo(), do: :overridable

        defoverridable Kernel.ImplTest.Behaviour

        def foo(), do: :overridden

        @impl true
        defmacro bar(), do: :overridden
      end
      """
    end) =~ "module attribute @impl was not set for function foo/0 callback (specified in Kernel.ImplTest.Behaviour)"
  end

  test "warns correctly for missing @impl even if it was set in overridable callback" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.MacroBehaviour

        @impl Kernel.ImplTest.Behaviour
        def foo(), do: :overridable

        defoverridable Kernel.ImplTest.Behaviour

        def foo(), do: :overridden

        @impl Kernel.ImplTest.MacroBehaviour
        defmacro bar(), do: :overridden
      end
      """
    end) =~ "module attribute @impl was not set for function foo/0 callback (specified in Kernel.ImplTest.Behaviour)"
  end

  test "warns correctly for incorrect @impl in overridable callback" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule Kernel.ImplTest.ImplAttributes do
        @behaviour Kernel.ImplTest.Behaviour
        @behaviour Kernel.ImplTest.MacroBehaviour

        @impl Kernel.ImplTest.MacroBehaviour
        def foo(), do: :overridable

        defoverridable Kernel.ImplTest.Behaviour

        @impl Kernel.ImplTest.Behaviour
        def foo(), do: :overridden
      end
      """
    end) =~ "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but this behaviour does not specify such callback"
  end

  test "does not warn for overridable callback when using __before_compile__/1 hook" do
    assert capture_err(fn ->
      Code.eval_string """
      defmodule BeforeCompile do
        defmacro __before_compile__(_) do
          quote do
            @behaviour Kernel.ImplTest.Behaviour

            def foo(), do: :overridable

            defoverridable Kernel.ImplTest.Behaviour
          end
        end
      end

      defmodule Kernel.ImplTest.ImplAttributes do
        @before_compile BeforeCompile
        @behaviour Kernel.ImplTest.MacroBehaviour

        defmacro bar(), do: :overridable

        defoverridable Kernel.ImplTest.MacroBehaviour

        @impl Kernel.ImplTest.MacroBehaviour
        defmacro bar(), do: :overridden
      end
      """
    end) == ""
  end
end

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

defmodule Kernel.ImplTest do
  use ExUnit.Case
