Skip to content

Commit

Permalink
Add mix mneme.watch (#83)
Browse files Browse the repository at this point in the history
Closes #82.
  • Loading branch information
zachallaun authored Jul 17, 2024
1 parent be16fee commit 3fbc482
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 48 deletions.
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.17.1-otp-27
erlang 27.0
elixir 1.17.2-otp-26
erlang 26.2.5.2
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ But, unlike ordinary tests, Mneme asks if you'd like the test updated for the ne
* **Seamless integration with ExUnit:** no need to change your workflow, just run `mix test`.
* **Interactive prompts in your terminal** when a new assertion is added or an existing one changes.
* **Syntax-aware diffs** highlight the meaningful changes in a value.
* **Built-in test watcher:** see changes immediately with `mix mneme.watch`.

## Interactive tour

Expand All @@ -62,7 +63,21 @@ $ elixir tour_mneme.exs
end
```

2. Add `:mneme` to your `:import_deps` in `.formatter.exs`:
2. Add a `:preferred_cli_env` entry for `mix mneme.watch` in `mix.exs`:

```elixir
def project do
[
...
preferred_cli_env: [
"mneme.watch": :test
],
...
]
end
```

3. Add `:mneme` to your `:import_deps` in `.formatter.exs`:

```elixir
[
Expand All @@ -71,14 +86,14 @@ $ elixir tour_mneme.exs
]
```

3. Start Mneme right after you start ExUnit in `test/test_helper.exs`:
4. Start Mneme right after you start ExUnit in `test/test_helper.exs`:

```elixir
ExUnit.start()
Mneme.start()
```

4. Add `use Mneme` wherever you `use ExUnit.Case`:
5. Add `use Mneme` wherever you `use ExUnit.Case`:

```elixir
defmodule MyTest do
Expand All @@ -91,7 +106,7 @@ $ elixir tour_mneme.exs
end
```

5. Run `mix test` and type `y<ENTER>` when prompted; your test should look like:
6. Run `mix test` and type `y<ENTER>` when prompted; your test should look like:

```elixir
defmodule MyTest do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
defmodule LRCPCorrect do
defmodule LRCP do
@moduledoc """
Parser for the ficticious Protohackers "Line Reversal Control Protocol".
For more, see here: https://protohackers.com/problem/7
"""

def parse("/data/" <> rest) do
with {:ok, session, rest} <- parse_integer(rest),
{:ok, position, rest} <- parse_integer(rest),
Expand All @@ -11,9 +17,8 @@ defmodule LRCPCorrect do
end

def parse("/connect/" <> rest) do
with {:ok, session, ""} <- parse_integer(rest) do
{:ok, {:connect, session: session}}
else
case parse_integer(rest) do
{:ok, session, ""} -> {:ok, {:connect, session: session}}
{:ok, _, rest} -> {:error, rest}
error -> error
end
Expand All @@ -30,9 +35,8 @@ defmodule LRCPCorrect do
end

def parse("/close/" <> rest) do
with {:ok, session, ""} <- parse_integer(rest) do
{:ok, {:close, session: session}}
else
case parse_integer(rest) do
{:ok, session, ""} -> {:ok, {:close, session: session}}
{:ok, _, rest} -> {:error, rest}
error -> error
end
Expand Down
2 changes: 0 additions & 2 deletions examples/demo_project/test/lrcp_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ defmodule LRCPTest do
use Mneme, dry_run: true

describe "parse/1" do
alias LRCPCorrect, as: LRCP

@tag :first_example
test "parses valid messages (1)" do
auto_assert LRCP.parse("/connect/12345/")
Expand Down
59 changes: 59 additions & 0 deletions lib/mix/tasks/mneme.watch.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Mix.Tasks.Mneme.Watch do
@shortdoc "Run tests when files change"
@moduledoc """
Runs the tests for a project when source files change.
This task is similar to [`mix test.watch`](https://hex.pm/packages/mix_test_watch),
but updated to work with Mneme:
* interrupts Mneme prompts, saving already-accepted changes
* doesn't re-trigger when test files are updated by Mneme
## Setup
To ensure `mix mneme.watch` runs in the test environment, add a
`:preferred_cli_env` entry in `mix.exs`:
def project do
[
...
preferred_cli_env: [
"mneme.watch": :test
],
...
]
end
## Command line options
This task runs `mix test` under the hood and passes all CLI arguments
to it directly. For instance:
```sh
# only run tests tagged with `some_tag: true`
$ mix mneme.watch --only some_tag
# only run tests from one file
$ mix mneme.watch test/my_app/my_test.exs
```
See the `mix test` documentation for more information.
"""

use Mix.Task

@doc false
@impl Mix.Task
@spec run([String.t()]) :: no_return()
def run(args) do
Mix.env(:test)

children = [
{Mneme.Watch.TestRunner, cli_args: args}
]

Supervisor.start_link(children, strategy: :one_for_one)

:timer.sleep(:infinity)
end
end
4 changes: 4 additions & 0 deletions lib/mneme/assertion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ defmodule Mneme.Assertion do
raise Mneme.InternalError, original_error: error, original_stacktrace: stacktrace
end

defp handle_assertion({:error, {:internal, error}}, _, _, _) do
raise Mneme.InternalError, original_error: error, original_stacktrace: []
end

defp eval(%{value: value, context: ctx} = assertion, env) do
binding = [{{:value, :mneme}, value} | ctx.binding]

Expand Down
14 changes: 7 additions & 7 deletions lib/mneme/patcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ defmodule Mneme.Patcher do
"""
@spec finalize!(state) :: :ok | {:error, term()}
def finalize!(project) do
project
|> Rewrite.paths()
|> Mneme.Watch.TestRunner.notify_about_to_save()

case Rewrite.write_all(project) do
{:ok, _project} ->
:ok
Expand All @@ -61,7 +65,7 @@ defmodule Mneme.Patcher do

case prepare_assertion(assertion, project) do
{:ok, {assertion, node}} ->
patch!(project, assertion, counter, node)
prompt_and_patch!(project, assertion, counter, node)

{:error, :not_found} ->
{{:error, :file_changed}, project}
Expand All @@ -71,11 +75,7 @@ defmodule Mneme.Patcher do
{{:error, {:internal, error, __STACKTRACE__}}, project}
end

defp patch!(_, %{value: :__mneme__super_secret_test_value_goes_boom__}, _, _) do
raise ArgumentError, "I told you!"
end

defp patch!(project, assertion, counter, node) do
defp prompt_and_patch!(project, assertion, counter, node) do
case prompt_change(assertion, counter) do
:accept ->
ast = replace_assertion_node(node, assertion.code)
Expand All @@ -100,7 +100,7 @@ defmodule Mneme.Patcher do
{{:error, :skipped}, project}

select ->
patch!(project, Assertion.select(assertion, select), counter, node)
prompt_and_patch!(project, Assertion.select(assertion, select), counter, node)
end
end

Expand Down
Loading

0 comments on commit 3fbc482

Please sign in to comment.