Skip to content

Commit

Permalink
wip: non-interrupting watcher working
Browse files Browse the repository at this point in the history
  • Loading branch information
zachallaun committed Jul 15, 2024
1 parent dd9cbd4 commit 1764032
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 2 deletions.
11 changes: 11 additions & 0 deletions lib/mix/tasks/mneme.watch.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Mix.Tasks.Mneme.Watch do
@shortdoc "Re-runs tests on save, interrupting Mneme prompts"
@moduledoc """
TODO
"""

use Mix.Task

@impl Mix.Task
defdelegate run(args), to: Mneme.Watch
end
8 changes: 8 additions & 0 deletions lib/mneme.ex
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ defmodule Mneme do
"""
@doc section: :setup
def start(opts \\ []) do
maybe_reenable_ansi()

opts =
if Keyword.has_key?(opts, :restart) do
[
Expand All @@ -389,6 +391,12 @@ defmodule Mneme do
:ok
end

defp maybe_reenable_ansi do
if System.get_env("MIX_MNEME_WATCH") == "true" do
Application.put_env(:elixir, :ansi_enabled, true)
end
end

defp start_server! do
children = [
Mneme.Server
Expand Down
117 changes: 117 additions & 0 deletions lib/mneme/watch.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
defmodule Mneme.Watch do
@moduledoc false

use GenServer

@doc """
Runs `mix.test` with the given CLI arguments, restarting when files change.
"""
@spec run([String.t()]) :: no_return()
def run(args \\ []) do
Mix.env(:test)
ensure_os!()

:ok = Application.ensure_started(:file_system)

children = [
{__MODULE__, cli_args: args}
]

Supervisor.start_link(children, strategy: :one_for_one)

:timer.sleep(:infinity)
end

@doc false
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end

@impl GenServer
def init(opts) do
Code.compiler_options(ignore_module_conflict: true)

state = opts |> Keyword.validate!([:cli_args]) |> Map.new()
file_system_opts = [dirs: [File.cwd!()], name: :mneme_file_system_watcher]

case FileSystem.start_link(file_system_opts) do
{:ok, _} ->
FileSystem.subscribe(:mneme_file_system_watcher)
{:ok, state, {:continue, :first_run}}

other ->
other
end
end

@impl GenServer
def handle_continue(:first_run, state) do
Mix.Task.run(:test, state.cli_args)
flush()

{:noreply, state}
end

@impl GenServer
def handle_info({:file_event, _pid, {path, _events}}, state) do
path = Path.relative_to_cwd(path)

if watching?(path) do
IO.puts("detected change: #{path}")
run_tests(state.cli_args)
flush()
end

{:noreply, state}
end

defp watching?(path) do
watching_directory?(path) and watching_extension?(path)
end

defp watching_directory?(path) do
ignored = ~w(deps/ _build/ .lexical/ .elixir_ls/ .elixir-tools/)
not String.starts_with?(path, ignored)
end

defp watching_extension?(path) do
watching = ~w(.erl .ex .exs .eex .leex .heex .xrl .yrl .hrl)
Path.extname(path) in watching
end

defp run_tests(cli_args) do
Code.unrequire_files(Code.required_files())
IEx.Helpers.recompile()

Check warning on line 84 in lib/mneme/watch.ex

View workflow job for this annotation

GitHub Actions / lint (1.17, 27)

unknown_function

Function IEx.Helpers.recompile/0 does not exist.
Mix.Task.reenable(:test)
Mix.Task.run(:test, cli_args)
end

defp flush do
receive do
_ -> flush()
after
0 -> :ok
end
end

defp ensure_os! do
case os_type() do
:unix ->
:ok

unsupported ->
error = "file watcher is unsupported on OS: #{inspect(unsupported)}"

[:red, "error: ", :default_color, error]
|> IO.ANSI.format()
|> then(&IO.puts(:stderr, &1))

System.halt(1)
end
end

defp os_type do
{os_type, _} = :os.type()
os_type
end
end
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ defmodule Mneme.MixProject do
{:nimble_options, "~> 1.0"},
{:sourceror, "~> 1.0"},
{:rewrite, "~> 0.10.1"},
{:file_system, "~> 1.0"},

# Development / Test
{:benchee, "~> 1.0", only: :dev},
Expand Down Expand Up @@ -83,7 +84,8 @@ defmodule Mneme.MixProject do
dialyzer: :test,
coveralls: :test,
"coveralls.html": :test,
"test.mneme_not_started": :test
"test.mneme_not_started": :test,
"mneme.watch": :test
]
end

Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"},
"excoveralls": {:hex, :excoveralls, "0.18.1", "a6f547570c6b24ec13f122a5634833a063aec49218f6fff27de9df693a15588c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d65f79db146bb20399f23046015974de0079668b9abb2f5aac074d078da60b8d"},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
Expand Down
2 changes: 1 addition & 1 deletion test/mneme/example_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule Mneme.ExampleTest do
end

test "2" do

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.16, 24)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.15, 24)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.14, 24)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.16, 26)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.17, 27)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.17, 25)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.15, 26)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.14, 26)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.17, 26)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.15, 25)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.16, 25)

test 2 (Mneme.ExampleTest)

Check failure on line 26 in test/mneme/example_test.exs

View workflow job for this annotation

GitHub Actions / test (1.14, 25)

test 2 (Mneme.ExampleTest)
s2 = %MyStruct{field: 5}
s2 = %MyStruct{field: 1}

auto_assert %MyStruct{field: 5, list: [:foo, :buzz]} <-
%{
Expand Down

0 comments on commit 1764032

Please sign in to comment.