Testing multi-letter ~SIGIL in library that also supports 1.14

Hi all! Curious if someone could share a pattern for conditional tests where the language parser changed. If the Elixir version is >= 1.15.0, I want to test this file, otherwise I don’t want it read it at all because parsing will fail.

One option might be using a suffix other than _test.exs for this particular test (like _test_1_15.exs), and then manually importing the file in my test helper if the Elixir version supports it. I’m wondering if there is another convention used elsewhere, though.

I assume you are aware of Version.match?/2 so you must be looking for something beyond that?

I am not aware of a pattern (though I am not good at those) so I’d probably just do:

if Version.match?(System.version(), "< 1.15.0") do
  # do tests that would blow up if using 1.15.0 or above.
end

in the test files themselves, while keeping their _test.exs format.

That technique works great when you’re dealing with a new API – a function or arity or something that was added in a later version. But with multi-letter sigils, it’s the parsing step that fails.

$ echo 'if Version.match?(System.version(), ">= 1.15.0"), do: ~SIGIL""' > scratch.exs

$ asdf shell elixir 1.15.0-otp-25
$ elixir scratch.exs
error: undefined function sigil_SIGIL/2 (there is no such import)
  scratch.exs:1

** (CompileError) scratch.exs: cannot compile file (errors have been logged)
    (stdlib 4.0.1) lists.erl:1462: :lists.mapfoldl_1/3
    (stdlib 4.0.1) lists.erl:1463: :lists.mapfoldl_1/3
    (stdlib 4.0.1) lists.erl:1462: :lists.mapfoldl_1/3
    (elixir 1.15.0) expanding macro: Kernel.if/2

$ asdf shell elixir 1.14.5-otp-25
$ elixir scratch.exs
** (SyntaxError) scratch.exs:1:55: invalid sigil delimiter: "I" (column 57, code point U+0049). The available delimiters are: //, ||, "", '', (), [], {}, <>
    |
  1 | if Version.match?(System.version(), ">= 1.15.0"), do: ~SIGIL""
    |                                                       ^
    (elixir 1.14.5) lib/code.ex:1260: Code.require_file/2

So the test file can’t even load in earlier versions, which is the issue for me here.

Ah, I see. Then perhaps instead of directly using the sigil thingy you can do Code.require_file in the do clause of the if?

This works:

# scratch.exs
if Version.match?(System.version(), ">= 1.15.0") do
  Code.require_file("e_1_15.exs")
else
  IO.puts("Skipping test because they can't run below Elixir 1.15")
end
# e_1_15.exs
~SIGIL""
asdf local elixir 1.15.0-otp-25
elixir scratch.exs
error: undefined function sigil_SIGIL/2 (there is no such import)
  e_1_15.exs:1

** (CompileError) e_1_15.exs: cannot compile file (errors have been logged)
    (elixir 1.15.0) lib/code.ex:1432: Code.require_file/2

Which is normal, as we don’t have that sigil defined.

And then:

asdf local elixir 1.14.5-otp-25
elixir scratch.exs
Skipping test because they can't run below Elixir 1.15
2 Likes

I think that’s what I’ll go with! Thanks.

There is also a configuration for test paths in your mix.exs, so you could have a special directory for v1.15 tests, but I think Code.require_file is fine. Or alternatively use one of the Code.eval_* in your tests.

3 Likes

I thought about test paths, but I think I read that each test path required it’s own helper and I have some config I didn’t want to duplicate.

I’ll go with require_file for now, but I should have thought of just evaling a string. Thanks!