Skip to content

Commit d0c3fe9

Browse files
authored
feat: add semantic diffs (#8)
1 parent 4b2ce4f commit d0c3fe9

29 files changed

+3278
-47
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
strategy:
6565
fail-fast: false
6666
matrix:
67-
elixir: [1.13, 1.14]
67+
elixir: [1.14]
6868
otp: [24.3, 25]
6969
steps:
7070
- uses: actions/checkout@v3

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ mneme-*.tar
2929

3030
# Scratch code
3131
scratch.exs
32+
33+
# Benchmark results
34+
/benchmarks/*.benchee

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5+
## Unreleased
6+
7+
### Breaking
8+
9+
* Mneme now requires Elixir v1.14 or later.
10+
11+
### Enhancements
12+
13+
* Introduce semantic diffs, which selectively highlights only meaningful changes when updating an assertion. This can be enabled with the `diff: :semantic` option, though it may become the default in the future. See the "Configuration" section of the `Mneme` module doc for more.
14+
515
## v0.1.6 (2023-03-04)
616

717
### Enhancements

benchmarks/diff.exs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# mix run benchmarks/diff.exs
2+
#
3+
# The first run will save benchmark results to a file and subsequent
4+
# results will compare to that file.
5+
#
6+
# Set env var `BENCHEE_SAVE=true` to overwrite the saved results.
7+
# Set env var `BENCHEE_PROFILE=true` to run profiler.
8+
9+
save_file = Path.expand("diff.benchee", __DIR__)
10+
11+
inputs = [
12+
simple: {
13+
"auto_assert Function.identity(self())",
14+
"auto_assert pid when is_pid(pid) <- Function.identity(self())"
15+
},
16+
moderate: {
17+
"""
18+
auto_assert {[["[:foo, ", %Tag{data: ":bar", sequences: [:red]}, ", :baz]"]],
19+
[["[", %Tag{data: ":bar", sequences: [:green]}, ", :foo, :baz]"]]} <-
20+
format("[:FOO, :BAR, [:BAZ]]", "[:BAR, :FOO, [:BAZ]]")
21+
""",
22+
"""
23+
auto_assert {[["[:FOO, ", %Tag{data: ":BAR", sequences: [:red]}, ", [:BAZ]]"]],
24+
[["[", %Tag{data: ":BAR", sequences: [:green]}, ", :FOO, :BAZ]"]]} <-
25+
format("[:FOO, :BAR, [:BAZ]]", "[:BAR, :FOO, [:BAZ]]")
26+
"""
27+
},
28+
complex: {
29+
"""
30+
auto_assert {:<<>>, [closing: [line: 1, column: 40], line: 1, column: 1],
31+
[
32+
{:int, [token: "0", line: 1, column: 3], 0},
33+
{:"::", [line: 1, column: 10],
34+
[
35+
{:var, [line: 1, column: 6], :head},
36+
{:-, [line: 1, column: 18],
37+
[
38+
{:var, [line: 1, column: 12], :binary},
39+
{:size, [closing: [line: 1, column: 25], line: 1, column: 19],
40+
[{:int, [token: "4", line: 1, column: 24], 4}]}
41+
]}
42+
]},
43+
{:"::", [line: 1, column: 32],
44+
[
45+
{:var, [line: 1, column: 28], :rest},
46+
{:var, [line: 1, column: 34], :binary}
47+
]}
48+
]} <- parse_string!("<<1, h::binary-size(2), more::binary>>")
49+
""",
50+
"""
51+
auto_assert {:<<>>, [closing: [line: 1, column: 37], line: 1, column: 1],
52+
[
53+
{:int, [token: "1", line: 1, column: 3], 1},
54+
{:"::", [line: 1, column: 7],
55+
[
56+
{:var, [line: 1, column: 6], :h},
57+
{:-, [line: 1, column: 15],
58+
[
59+
{:var, [line: 1, column: 9], :binary},
60+
{:size, [closing: [line: 1, column: 22], line: 1, column: 16],
61+
[{:int, [token: "2", line: 1, column: 21], 2}]}
62+
]}
63+
]},
64+
{:"::", [line: 1, column: 29],
65+
[
66+
{:var, [line: 1, column: 25], :more},
67+
{:var, [line: 1, column: 31], :binary}
68+
]}
69+
]} <- parse_string!("<<1, h::binary-size(2), more::binary>>")
70+
"""
71+
}
72+
]
73+
74+
profile? = System.get_env("BENCHEE_PROFILE") == "true"
75+
save? = !File.exists?(save_file) || System.get_env("BENCHEE_SAVE") == "true"
76+
77+
opts =
78+
cond do
79+
profile? -> [profile_after: true]
80+
save? -> [save: [path: save_file, tag: "baseline"]]
81+
true -> [load: save_file]
82+
end
83+
84+
Benchee.run(
85+
%{
86+
"Mneme.Diff.format/2" =>
87+
fn ->
88+
{left, right} = inputs[:moderate]
89+
Mneme.Diff.format(left, right)
90+
end
91+
},
92+
opts ++ [
93+
warmup: 1,
94+
time: 5,
95+
memory_time: 2
96+
]
97+
)

config/config.exs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Config
2+
3+
import_config "#{config_env()}.exs"

config/dev.exs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import Config

config/prod.exs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import Config

config/test.exs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Config
2+
3+
config :mneme,
4+
defaults: [
5+
diff: :semantic
6+
]

examples/tour_mneme.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Download and run in your terminal with: elixir tour_mneme.exs
33

44
Mix.install([
5-
{:mneme, "~> 0.0.5"}
5+
{:mneme, ">= 0.0.0"}
66
])
77

88
ExUnit.start(seed: 0)

lib/mneme.ex

+30-11
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,20 @@ defmodule Mneme do
147147
148148
## Configuration
149149
150-
There are a few controls that can be used to change Mneme's behavior
151-
when it runs auto-assertions. These can be set at the module-level by
152-
passing options to `use Mneme`, the `describe` level using the
153-
`@mneme_describe` attribute, or the `test` level using the `@mneme`
154-
attribute. For instance:
150+
Certain behavior can be configured globally using application config
151+
or locally in test modules either at the module, describe-block, or
152+
test level.
153+
154+
To configure Mneme globally, you can set `:defaults` for the `:mneme`
155+
application:
156+
157+
config :mneme,
158+
defaults: [
159+
diff: :semantic
160+
]
161+
162+
These defaults can be overriden in test modules at various levels
163+
either as options to `use Mneme` or as module attributes.
155164
156165
defmodule MyTest do
157166
use ExUnit.Case
@@ -179,23 +188,33 @@ defmodule Mneme do
179188
end
180189
end
181190
191+
Configuration that is "closer to the test" will override more general
192+
configuration:
193+
194+
@mneme > @mneme_describe > opts to use Mneme > :mneme app config
195+
196+
The exception to this is the `CI` environment variable, which causes
197+
all updates to be rejected. See the "Continuous Integration" section
198+
for more info.
199+
182200
### Options
183201
184202
#{Mneme.Options.docs()}
185203
186204
## Formatting
187205
188206
Mneme uses [`Rewrite`](https://github.com/hrzndhrn/rewrite) to update
189-
source source code, formatting that code before saving the file.
190-
Currently, the Elixir formatter and `FreedomFormatter` are supported.
191-
**If you do not use a formatter, the first auto-assertion will reformat
192-
the entire file.**
207+
source code, formatting that code before saving the file. Currently,
208+
the Elixir formatter and `FreedomFormatter` are supported. **If you do
209+
not use a formatter, the first auto-assertion will reformat the entire
210+
file.**
193211
194212
## Continuous Integration
195213
196214
In a CI environment, Mneme will not attempt to prompt and update any
197-
assertions. This behavior is enabled by the `CI` environment variable,
198-
which is set by convention by many continuous integration providers.
215+
assertions, but will instead fail any tests that would update. This
216+
behavior is enabled by the `CI` environment variable, which is set by
217+
convention by many continuous integration providers.
199218
200219
```bash
201220
export CI=true

0 commit comments

Comments
 (0)