defmodule Mix.Tasks.Test.Watch do use Mix.Task use GenServer @shortdoc "Automatically run tests on file changes" @moduledoc """ A task for running tests whenever source files change. """ def run(args) do args = Enum.join(args, " ") Application.start :fs, :permanent GenServer.start_link( __MODULE__, args, name: __MODULE__ ) :timer.sleep :infinity end # Genserver callbacks def init(args) do :fs.subscribe {:ok, %{ args: args }} end def handle_info({_pid, {:fs, :file_event}, {path, _event}}, state) do if watching?( to_string path ) do run_tests( state.args ) end {:noreply, state} end defp watching?(path) do Regex.match?( ~r/\.(ex|exs|eex)\z/i, path ) end defp run_tests(args) do IO.puts "\nRunning tests..." args |> mix_cmd |> shell_exec flush end defp mix_cmd(args) do ansi = "Application.put_env(:elixir, :ansi_enabled, true);" """ sh -c "MIX_ENV=test mix do run -e '#{ansi}', test #{args}" """ end defp shell_exec(exe) do args = ~w(stream binary exit_status use_stdio stderr_to_stdout)a Port.open({:spawn, exe}, args) |> results_loop end defp flush do receive do _ -> flush after 0 -> :ok end end defp results_loop(port) do receive do {^port, {:data, data}} -> IO.write(data) results_loop(port) {^port, {:exit_status, status}} -> status end end end