I can't get actual code location (line/column) from macros used in tests

I’m trying to build a protocol and a test suite to implement different adapters (impls of the protocol).

I tried to use a CaseTemplate, or just regular macros, but all tests defined inside quote location: :keep ... will always show, on failure, the line where the macro is called or where the template is used.

Any idea how I could have the actual line of failure?

Thank you

1 Like

I’m not sure if I understand correctly, but maybe this helps:

defmacro foo() do
  bar(__CALLER__)
end

def bar(caller) do
  meta = Map.take(caller, [:file, :line])
  ...
end

Unfortunately not, it is not what I am looking for. But thank you!

The thing is that if you use a CaseTemplate like this:

defmodule Demo.TestSuite do
  use ExUnit.CaseTemplate

  using do
    quote location: :keep do
      test "some test" do
        assert 1 == 2
      end
    end
  end
end

And a test like this:

defmodule Demo.SomeTest do
  use Demo.TestSuite
end

The result will be this:

  1) test some test (Demo.SomeTest)
     test/some_test.exs:2
     Assertion with == failed
     code:  assert 1 == 2
     left:  1
     right: 2
     stacktrace:
       test/some_test.exs:2: (test)

You cannot get the actual line of thest that failed in the stacktrace property.

If I understand you right you want to see the file that defines Demo.SomeTest in the error, and if I understand the docs correctly, that’ll be the case if you remove : location.

https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2-stacktrace-information

1 Like

It is the opposite. Defining location: :keep or not does not change anything. In both cases, the stacktrace is shown as test/some_test.exs:2: (test), i.e. the Demo.SomeTest module where use Demo.TestSuite is called.

I expect to see like to see the actual location of the failure: test/support/suite.ex:7: as the docs you linked suggest. (now line 10 instead of 7 in the updated version below) But somehow, a call to test inside a quote overrides this behaviour of location keep.

See this updated example:

# suite.ex
defmodule Demo.TestSuite do
  use ExUnit.CaseTemplate

  using do
    quote do
      require unquote(__MODULE__)

      test "some test" do
        assert 1 == 2
      end
    end
  end

  defmacro define_failing do
    quote do
      def failing_fun() do
        ExUnit.Assertions.assert(false)
      end
    end
  end

  defmacro define_failing_with_location do
    quote location: :keep do
      def failing_fun_with_location() do
        ExUnit.Assertions.assert(false)
      end
    end
  end
end
# some_text.exs
defmodule Demo.SomeTest do
  use Demo.TestSuite

  Demo.TestSuite.define_failing()

  test "macro" do
    failing_fun()
  end

  Demo.TestSuite.define_failing_with_location()

  test "macro with location" do
    failing_fun_with_location()
  end
end

This gives us this (truncated) output:

  2) test macro (Demo.SomeTest)
     test/some_test.exs:6
     Expected truthy, got false
     code: assert false
     stacktrace:
       test/some_test.exs:4: Demo.SomeTest.failing_fun/0



  3) test macro with location (Demo.SomeTest)
     test/some_test.exs:12
     Expected truthy, got false
     code: assert false
     stacktrace:
       test/support/suite.ex:25: Demo.SomeTest.failing_fun_with_location/0

Here we can see that with location: :keep, the actual call site for assert/1 (suite.ex:25) is reported. This is what I want.

So that would be my workaround: each test defined in the suite would contain a single external call to a function that actually implements the test. But it is convoluted for no apparent reason.

Looks like you can’t do anything about that, test macro uses __CALLER__.file and enforces it.

perhaps you can use a similar technique to override it?

1 Like

that’s what I thought. Try to bring the __CALLER__ down to your code yourself.

Well I don’t want to add some hackish macro code above 50 or more tests. I guess I will resort to call a test implementation function inside each test.