fireproofsocks

fireproofsocks

This is a problem I’ve come across now and then… in an app, I have a few directories containing .json files (e.g. for example requests or responses). I put these files into directories inside of test/support/ and then in my mix.exs I reference the test/support/ directory as follows:

    def project do
    [
      # ...
      elixirc_paths: elixirc_paths(Mix.env()),
      # ...
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

The problem revolves around the __DIR__ variable because it is resolved at compile time. I can include files in my setup fixtures doing something like this:

 # example setup inside `test/support/conn_case.ex`
  defp append_mutation(%{mutation: mutation} = context) do
    data =
      "#{__DIR__}/mutations/#{mutation}.json"  # <-- resolves to /full/path/to/test/support/mutations/something.json
      |> File.read!()
      |> Jason.decode!()

    %{context | mutation: data}
  end

When I run tests locally, the path gets converted to paths that make sense on my local machine, so tests pass. However, if I then try to run tests in a containerized environment (e.g. via docker-compose), the paths are not the same, so tests fail.

One option is to do mix compile --force before running the tests, but that’s slow – I have to recompile everything every time I switch environments.

I seem to remember something about the Application — Elixir v1.20.2 working in certain cases here… but I don’t see how it’s working its magic. Even with my mix.exs set to compile my test/support/ folder, none of my supporting .json files end up in the _build/ folder, so I don’t think I can reference them there.

Can someone educate me on a better way to dealing with this?

Thanks!

Showing Posts 1 to 10

NobbZ

NobbZ

You should compile the files in the docker, not on your system.

Another approach were to compile the raw JSON I to the module.

fireproofsocks

fireproofsocks OP

Another approach were to compile the raw JSON I to the module.

How? I thought I was doing that by specifying the test/support/ directory in my elixirc_paths.

NobbZ

NobbZ

That just specifies that *.ex files in test/support should be compiled into *.beam files on disk.

This does not cause any JSON to be compiled into the modules. They JSON is still loaded during runtime.

The approach I am proposing does the following:

defmodule Foo do
  @mutations ~s[mutation1 mutation2 mutation3]a
  @json_data @mutations
    |> Stream.map(fn mutation -> {mutation, Path.join([__DIR__, "mutations", "#{mutation}.json"]) |> File.read! |> Jason.decode!} end)
    |> Map.new
  
  @mutations |> Enum.each(fn {mutation, json} ->
    @external_resource Path.join([__DIR__, "mutations", "#{mutation}.json"])
    def json_data(unquote(mutation)), do: unquote(json)
  end

  defp append_mutation(…) do
    data = json_data(mutation)
    %{context | mutation: data}
  end
end

This will only work though if the JSON file is not written to after compilation and if the names are knwon statically. On the other hand side, touching any of the files will cause the module in question to recompile as necessary.

If though the files are subject to change during the test as part of the testrun, you should not store them in the project folder at all, but somewhere in /tmp, making sure they won’t interfere in subsequent testruns.

fireproofsocks

fireproofsocks OP

Won’t that have the same problems because __DIR__ will get evaluated at compile time?

NobbZ

NobbZ

Yes, __DIR__ will get evaluated at compiletime. But no file access will happen at runtime. The JSON will be parsed at compile time and put as an immediate value into the compiled module.

So instead of having some path in your sources pointing to a JSON file containing {} on the build but not existing on the run time host, you now have a module that not contains that path at all, but instead a function that returns the immediate value %{} when asked for that mutations name.

fireproofsocks

fireproofsocks OP

Ah, that’s tricky. Thanks!

joshtaylor

joshtaylor

Can you do:

Path.join([:code.priv_dir(:my_otp_app), "#{mutation}.json"])

?

fireproofsocks

fireproofsocks OP

That looks promising – it resolves to /path/to/my_app/_build/test/lib/my_app/priv/, which in my case is empty.

I see that gettext has some extra files that end up in there (its .po files)… but I don’t see how to get those included… and I can’t find docs as to how mix is supposed to handle this.

From gettext 's mix.exs:

  def project do
    [
      # ... 
      app: :gettext,
      package: hex_package(),
      # ...
    ]
  end

  def hex_package do
    [
      maintainers: ["Andrea Leopardi", "José Valim"],
      licenses: ["Apache 2.0"],
      links: %{"GitHub" => @repo_url},
      files: ~w(lib src/gettext_po_parser.yrl mix.exs *.md)
    ]
  end
kip

kip

ex_cldr Core Team

the .po files are compiled into functions in the module that use Gettext, otp_app: ___ so they don’t need to be in a release.

The behaviour of the priv dir is controlled by the build_embedded: Mix.env() == :prod, line in mix.exs. In development the priv dir is symlinked to the _build directory and in production it is copied. So I would expect your /path/to/my_app/_build/test/lib/my_app/priv/ is a symlink on your dev machine?

For example, for one of my libs:

$ ls -al _build/test/lib/ex_cldr/priv
lrwxr-xr-x  1 kip  staff  16  8 Jan 06:09 _build/test/lib/ex_cldr/priv -> ../../../../priv
fireproofsocks

fireproofsocks OP

Interesting. So it looks like it would be possible to put some extra files into the priv/ directory (in my case .json files), but that feels a bit odd because they are only relevant to testing…

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews