1
votes

When running tests in my Elixir mix app, I have a few instances where I include some calls to Logger.debug() to monitor behavior. I find that, when I call mix test from the command line, the Logger output is colored light-blue, and if I do something like add a custom color to the .debug() call, the output is colored accordingly -

test "some behavior" do
  Logger.debug("foo", ansi_color: :yellow)
  assert true
end

writes the log lines in yellow, as expected.

However, I've noticed that if I run my tests from within an iex session, the colors are not applied, just written to the terminal in the default font color (gray, in my case) -

iex(1)> Mix.shell().cmd(
          "mix test --color",
          env: [{"MIX_ENV", "test"}]
        )

Why is it that running the tests from the bash command line results in colorful output, while running them from within iex results in my default font color?

For context - I'm running this on a Mac (Mojave), from the bash command line (as mentioned), Elixir version 1.8.1.

Admittedly, this is a fairly trivial matter in itself - but I'm trying to get a better understanding of the Elixir shell and Mix's various interactions with the shell and system, and cracking this might help me to get a better understanding of what's going on under the hood in general. It's not what I would expect to happen, I'd like to know why.

2

2 Answers

3
votes

Explanation is in this and this article, but TL;DR below:

You need to enable colors in iex via:

iex> Application.put_env(:elixir, :ansi_enabled, true)

but this is session scope only. If you want to make it permanent you need to create .iex.exs file in current directory or your home directory:

timestamp = fn ->
  {_date, {hour, minute, _second}} = :calendar.local_time
  [hour, minute]
  |> Enum.map(&(String.pad_leading(Integer.to_string(&1), 2, "0")))
  |> Enum.join(":")
end

IEx.configure(
  colors: [
    syntax_colors: [
      number: :light_yellow,
      atom: :light_cyan,
      string: :light_black,
      boolean: :red,
      nil: [:magenta, :bright],
    ],
    ls_directory: :cyan,
    ls_device: :yellow,
    doc_code: :green,
    doc_inline_code: :magenta,
    doc_headings: [:cyan, :underline],
    doc_title: [:cyan, :bright, :underline],
  ],
)

IEx.configure(
  default_prompt:
    "#{IO.ANSI.green}%prefix#{IO.ANSI.reset} " <>
    "[#{IO.ANSI.magenta}#{timestamp.()}#{IO.ANSI.reset} " <>
    ":: #{IO.ANSI.cyan}%counter#{IO.ANSI.reset}] >"
)

And that's all

2
votes

The reasoning is dumb simple - the stdout has different characteristics when it is run with "real" terminal attached to the stdout and when it is pipe or file. The same behaviour can be seen when you run:

mix test | cat

As this will attach pipe, not "real" terminal (in fact it is terminal emulator, but that is out of scope of the question). This mean that Elixir cannot assume that the resulting stream will support ANSI escape codes. This is important when you pipe the commands to separate tools or files for further processing, as a lot of these tools do not understand ANSI escape codes and would result with wrong results.

The best way to run the mix test from within IEx and keep all current configuration is to simply do:

Mix.Task.rerun(:test)

And this will reuse current configuration. Of course that will have disadvantage that it will run with the current environment, but that can be fixed as well via running MIX_ENV=test iex -S mix to have access to whole test environment within shell.