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.

Hey guys, long time since this question was asked but I was just looking for the same answer and lucikly I found a workaround, in Elixir 1.18.3 (compiled with Erlang/OTP 27).

Following the recommendations from the Macros docs, this is how I’m able to get a stacktrace ending with the exact line where the assertion error is happening, in the CaseTemplate file:

defmodule Demo.TestSuite do
  use ExUnit.CaseTemplate

  using do
    quote do
      import Demo.TestSuite

      test "some test" do
        assert_eq(1, 2)
      end
    end
  end

  def assert_eq(a, b) do
    assert a == b
    true # WITHOUT this, it just shows the line in the test file at the top of the stacktrace
  end
end

I’m returning true because the function starts with assert_. I would return false if the function starts with refute_

I don’t know the exact reason of this behavior, I just noticed that if the last statement in the function is an assert or refute, the output shows only the line where the function is called, and not the line inside the function.

1 Like