Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mix mneme.watch #83

Merged
merged 24 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fd3d85f
wip: non-interrupting watcher working
zachallaun Jul 15, 2024
bf9e508
docs: add `:preferred_cli_env` entry suggestion for `mneme.watch`
zachallaun Jul 15, 2024
76e326e
fix: dialyzer error from iex being unavailable
zachallaun Jul 15, 2024
39884c4
chore: move all watch logic back into task
zachallaun Jul 15, 2024
05932c0
refactor: run patcher in a task
zachallaun Jul 15, 2024
c28d244
fix: only watch paths in elixirc_paths, erlc_paths, and test_paths
zachallaun Jul 15, 2024
5cb7ea7
feat: accumulate saved files while test is running
zachallaun Jul 15, 2024
ada3914
wip: interrupt running tests on file event
zachallaun Jul 15, 2024
73de2ee
fix: ignore no_unknown dialyzer warning
zachallaun Jul 16, 2024
47f104a
wip: skip all remaining auto assertions on file change
zachallaun Jul 16, 2024
4fec110
test: use patch instead of secret value to test internal error
zachallaun Jul 17, 2024
9f752c9
refactor: move server register_assertion code into helper
zachallaun Jul 17, 2024
58a3396
refactor: wrap file system watcher as `Mneme.Watch.ElixirFiles`
zachallaun Jul 17, 2024
62f730a
refactor: move bulk of mneme.watch task into `Mneme.Watch.TestRunner`
zachallaun Jul 17, 2024
0c9b94e
build: use latest elixir but drop down to otp 26
zachallaun Jul 17, 2024
5428106
fix: prevent mneme.watch from re-running tests after mneme updates them
zachallaun Jul 17, 2024
2ba36c5
docs: update mix mneme.watch docs
zachallaun Jul 17, 2024
4c05526
docs: update example demo project
zachallaun Jul 17, 2024
bffca12
fix: dialyzer error
zachallaun Jul 17, 2024
1a7a97f
docs: add note about mneme.watch to readme
zachallaun Jul 17, 2024
08b9790
chore: remove os restriction on mix mneme.watch
zachallaun Jul 17, 2024
a54609f
chore: bump version to 0.9.0-dev
zachallaun Jul 17, 2024
32a70c0
docs: update mix mneme.watch moduledoc
zachallaun Jul 17, 2024
9d6b126
chore: remove dead code from earlier experiment
zachallaun Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading