zekus

zekus

Testing escripts with Sytem.exit()

Hello all.

When writing an Escript that uses System.exit/1 Is there a way to test it?
Or is there a better way to make an Escript return the proper exit code which is testable with ExUnit?

Thanks

Marked As Solved

mbklein

mbklein

Assuming you’re writing this script in a full mix-based environment, I’d probably test this one (or both!) of two ways:

The first would be to make the escript itself a very thin wrapper around the actual functionality it invokes, so that functionality can be tested with more conventional unit tests. You can use Code.compile_file/2 to load and compile your .exs in the test setup, call its main function yourself from inside the test, and unload it after:

defmodule MyScriptTest do
  use ExUnit.Case

  setup do
    Code.compile_file("path/to/escript.exs")
    on_exit(fn ->
      :code.delete(MyEscriptModule)
      :code.purge(MyEscriptModule)
    end)
  end

  test "test the thing" do
    with result <- MyEscriptModule.CLI.main(args) do
      assert something_about(result)
    end
  end
end

The second would be to wrap the System.exit/1 call in its own tiny module:

defmodule MyEscriptModule do
  defmodule Terminator do
    def terminate(status), do: System.exit(status)
  end

  defmodule CLI do
    def main(args) do
      # ... do whatever your script does
      terminate(some_status)
    do

    defp terminate(status) do
      Application.get_env(:my_app, :terminator, Terminator)
      |> Kernel.apply(:terminate, [status])
    end
  end
end

Then in your test, create a mock terminator module you can spy on:

defmodule MyScriptTest do
  use ExUnit.Case

  setup do
    code =
      quote do
        def terminate(status), do: unquote(self()) |> send({:exit_code, status})
      end

    Module.create(TestTerminator, code, file: __ENV__.file)
    Application.put_env(:my_app, :terminator, TestTerminator)

    on_exit(fn ->
      Application.delete_env(:my_app, :terminator)
      :code.delete(TestTerminator)
      :code.purge(TestTerminator)
    end)
  end

  test "test the thing" do
    with result <- MyEscriptModule.CLI.main(args) do
      assert_received({:exit_code, expected_exit_status})
    end
  end
end

There’s probably a much simpler way to structure this, especially the TestTerminator, but I don’t know how you’d get the test process’ PID into the mock without constructing it that way. Someone else might have a tighter example.

Where Next?

Popular in Questions Top

JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement