1Code.require_file("../test_helper.exs", __DIR__)
2
3defmodule ExUnit.DiffTest do
4  use ExUnit.Case, async: true
5
6  alias Inspect.Algebra
7  alias ExUnit.{Assertions, Diff}
8
9  defmodule User do
10    defstruct [:name, :age]
11  end
12
13  defmodule Person do
14    defstruct [:age]
15  end
16
17  defmodule Opaque do
18    defstruct [:data]
19
20    defimpl Inspect do
21      def inspect(%{data: data}, _) when is_tuple(data) or is_map(data),
22        do: "#Opaque<data: #{inspect(data)}>"
23
24      def inspect(_, _),
25        do: "#Opaque<???>"
26    end
27  end
28
29  defmacrop one, do: 1
30
31  defmacrop tuple(a, b) do
32    quote do
33      {unquote(a), unquote(b)}
34    end
35  end
36
37  defmacrop pin_x do
38    x = Macro.var(:x, nil)
39    quote(do: ^unquote(x))
40  end
41
42  defmacrop assert_diff(expr, expected_binding, pins \\ [])
43
44  defmacrop assert_diff({:=, _, [left, right]}, expected_binding, pins) do
45    left = Assertions.__expand_pattern__(left, __CALLER__) |> Macro.escape()
46
47    quote do
48      assert_diff(
49        unquote(left),
50        unquote(right),
51        unquote(expected_binding),
52        {:match, unquote(pins)}
53      )
54    end
55  end
56
57  defmacrop assert_diff({op, _, [left, right]}, [], []) when op in [:==, :===] do
58    quote do
59      assert_diff(unquote(left), unquote(right), [], unquote(op))
60    end
61  end
62
63  defmacrop refute_diff(expr, expected_left, expected_right, pins \\ [])
64
65  defmacrop refute_diff({:=, _, [left, right]}, expected_left, expected_right, pins) do
66    left = Assertions.__expand_pattern__(left, __CALLER__) |> Macro.escape()
67
68    quote do
69      refute_diff(
70        unquote(left),
71        unquote(right),
72        unquote(expected_left),
73        unquote(expected_right),
74        {:match, unquote(pins)}
75      )
76    end
77  end
78
79  defmacrop refute_diff({op, _, [left, right]}, expected_left, expected_right, [])
80            when op in [:==, :===] do
81    quote do
82      refute_diff(
83        unquote(left),
84        unquote(right),
85        unquote(expected_left),
86        unquote(expected_right),
87        unquote(op)
88      )
89    end
90  end
91
92  defp refute_diff(left, right, expected_left, expected_right, context) do
93    {diff, _env} = Diff.compute(left, right, context)
94    assert diff.equivalent? == false
95
96    diff_left = to_diff(diff.left, "-")
97    assert diff_left =~ expected_left
98
99    diff_right = to_diff(diff.right, "+")
100    assert diff_right =~ expected_right
101  end
102
103  defp assert_diff(left, right, expected_binding, context) do
104    {diff, env} = Diff.compute(left, right, context)
105    env_binding = for {{name, _}, value} <- env.current_vars, do: {name, value}
106
107    assert diff.equivalent? == true
108    assert env_binding == expected_binding
109  end
110
111  defp to_diff(side, sign) do
112    side
113    |> Diff.to_algebra(&diff_wrapper(&1, sign))
114    |> Algebra.format(:infinity)
115    |> IO.iodata_to_binary()
116  end
117
118  defp diff_wrapper(doc, side) do
119    Algebra.concat([side, doc, side])
120  end
121
122  test "atoms" do
123    assert_diff(:a = :a, [])
124    assert_diff(:a = :a, [])
125    assert_diff(:"$a" = :"$a", [])
126
127    refute_diff(:a = :b, "-:a-", "+:b+")
128    refute_diff(:a = :aa, "-:a-", "+:aa+")
129
130    refute_diff(:"$" = :"$a", ~s[-:"$"-], ~s[+:"$a"+])
131    refute_diff(:"$a" = :"$b", ~s[-:"$a"-], ~s[+:"$b"+])
132
133    refute_diff(:bar = 42, "-:bar-", "+42+")
134    refute_diff(42 = :bar, "-42-", "+:bar+")
135
136    pins = %{{:a, nil} => :a, {:b, nil} => :b}
137    assert_diff(x = :a, [x: :a], pins)
138    assert_diff(^a = :a, [], pins)
139    assert_diff(^b = :b, [], pins)
140
141    refute_diff(^a = :b, "-^a-", "+:b+", pins)
142    refute_diff(^b = :a, "-^b-", "+:a+", pins)
143  end
144
145  test "pseudo vars" do
146    assert_diff(__MODULE__ = ExUnit.DiffTest, [])
147    refute_diff(__MODULE__ = SomethingElse, "-__MODULE__-", "+SomethingElse+")
148  end
149
150  test "integers" do
151    assert_diff(123 = 123, [])
152    assert_diff(-123 = -123, [])
153    assert_diff(123 = +123, [])
154    assert_diff(+123 = 123, [])
155
156    refute_diff(12 = 13, "1-2-", "1+3+")
157    refute_diff(12345 = 123, "123-45-", "123")
158    refute_diff(123 = 12345, "123", "123+45+")
159    refute_diff(12345 = 345, "-12-345", "345")
160    refute_diff(345 = 12345, "345", "+12+345")
161    refute_diff(123 = -123, "123", "+-+123")
162    refute_diff(-123 = 123, "---123", "123")
163    refute_diff(491_512_235 = 490_512_035, "49-1-512-2-35", "49+0+512+0+35")
164
165    assert_diff(0xF = 15, [])
166    refute_diff(0xF = 16, "1-5-", "1+6+")
167    refute_diff(123 = :a, "-123-", "+:a+")
168  end
169
170  test "floats" do
171    assert_diff(123.0 = 123.0, [])
172    assert_diff(-123.0 = -123.0, [])
173    assert_diff(123.0 = +123.0, [])
174    assert_diff(+123.0 = 123.0, [])
175
176    refute_diff(1.2 = 1.3, "1.-2-", "1.+3+")
177    refute_diff(12.345 = 12.3, "12.3-45-", "12.3")
178    refute_diff(12.3 = 12.345, "12.3", "12.3+45+")
179    refute_diff(123.45 = 3.45, "-12-3.45", "3.45")
180    refute_diff(3.45 = 123.45, "3.45", "+12+3.45")
181    refute_diff(1.23 = -1.23, "1.23", "+-+1.23")
182    refute_diff(-1.23 = 1.23, "---1.23", "1.23")
183    refute_diff(123.0 = :a, "-123.0-", "+:a+")
184    refute_diff(123.0 = 123_512_235, "-123.0-", "+123512235+")
185  end
186
187  test "== / ===" do
188    refute_diff(
189      %{a: 1, b: 2} == %{a: 1.0, b: :two},
190      "%{a: 1, b: -2-}",
191      "%{a: 1.0, b: +:two+}"
192    )
193
194    refute_diff(
195      %{a: 1, b: 2} === %{a: 1.0, b: :two},
196      "%{a: -1-, b: -2-}",
197      "%{a: +1.0+, b: +:two+}"
198    )
199  end
200
201  test "lists" do
202    assert_diff([] = [], [])
203
204    assert_diff([:a] = [:a], [])
205    assert_diff([:a, :b, :c] = [:a, :b, :c], [])
206
207    refute_diff([] = [:a], "[]", "[+:a+]")
208    refute_diff([:a] = [], "[-:a-]", "[]")
209    refute_diff([:a] = [:b], "[-:a-]", "[+:b+]")
210    refute_diff([:a, :b, :c] = [:a, :b, :x], "[:a, :b, -:c-]", "[:a, :b, +:x+]")
211    refute_diff([:a, :x, :c] = [:a, :b, :c], "[:a, -:x-, :c]", "[:a, +:b+, :c]")
212    refute_diff([:a, :d, :b, :c] = [:a, :b, :c, :d], "[:a, -:d-, :b, :c]", "[:a, :b, :c, +:d+]")
213
214    refute_diff([:a, :b, :c] = [:a, :b, []], "[:a, :b, -:c-]", "[:a, :b, +[]+]")
215    refute_diff([:a, :b, []] = [:a, :b, :c], "[:a, :b, -[]-]", "[:a, :b, +:c+]")
216    refute_diff([:a, :b, :c] = [:a, :b], "[:a, :b, -:c-]", "[:a, :b]")
217    refute_diff([:a, :b] = [:a, :b, :c], "[:a, :b]", "[:a, :b, +:c+]")
218    refute_diff([:a, :b, :c, :d, :e] = [:a, :b], "[:a, :b, -:c-, -:d-, -:e-]", "[:a, :b]")
219    refute_diff([:a, :b] = [:a, :b, :c, :d, :e], "[:a, :b]", "[:a, :b, +:c+, +:d+, +:e+]")
220
221    refute_diff(
222      [:a, [:d, :b, :c]] = [:a, [:b, :c, :d]],
223      "[:a, [-:d-, :b, :c]]",
224      "[:a, [:b, :c, +:d+]]"
225    )
226
227    refute_diff(
228      [:e, :a, :b, :c, :d] = [:a, :b, :c, :d, :e],
229      "[-:e-, :a, :b, :c, :d]",
230      "[:a, :b, :c, :d, +:e+]"
231    )
232
233    refute_diff([:a, [:c, :b]] = [:a, [:b, :c]], "[:a, [-:c-, :b]]", "[:a, [:b, +:c+]]")
234    refute_diff(:a = [:a, [:b, :c]], "-:a-", "+[:a, [:b, :c]]+")
235
236    pins = %{{:a, nil} => :a, {:b, nil} => :b, {:list_ab, nil} => [:a, :b]}
237
238    assert_diff(x = [], [x: []], pins)
239    assert_diff(x = [:a, :b], [x: [:a, :b]], pins)
240    assert_diff([x] = [:a], [x: :a], pins)
241    assert_diff([x, :b, :c] = [:a, :b, :c], [x: :a], pins)
242    assert_diff([x, y, z] = [:a, :b, :c], [x: :a, y: :b, z: :c], pins)
243    assert_diff([x, x, :c] = [:a, :a, :c], [x: :a], pins)
244
245    refute_diff([x] = [], "[-x-]", "[]")
246    refute_diff([x, :b, :c] = [:a, :b, :x], "[x, :b, -:c-]", "[:a, :b, +:x+]")
247    refute_diff([x, x, :c] = [:a, :b, :c], "[x, -x-, :c]", "[:a, +:b+, :c]")
248
249    assert_diff(^list_ab = [:a, :b], [], pins)
250    assert_diff([^a, :b, :c] = [:a, :b, :c], [], pins)
251    assert_diff([^a, ^b, :c] = [:a, :b, :c], [], pins)
252    assert_diff([^a, a, :c] = [:a, :b, :c], [a: :b], pins)
253    assert_diff([b, ^b, :c] = [:a, :b, :c], [b: :a], pins)
254
255    refute_diff(^list_ab = [:x, :b], "-^list_ab-", "[+:x+, :b]", pins)
256    refute_diff([^a, :b, :c] = [:a, :b, :x], "[^a, :b, -:c-]", "[:a, :b, +:x+]", pins)
257    refute_diff([:a, ^a, :c] = [:a, :b, :c], "[:a, -^a-, :c]", "[:a, +:b+, :c]", pins)
258
259    refute_diff(
260      [x, :a, :b, :c, :d] = [:a, :b, :c, :d, :e],
261      "[x, -:a-, :b, :c, :d]",
262      "[:a, :b, :c, :d, +:e+]"
263    )
264
265    refute_diff([:a, :b] = :a, "-[:a, :b]-", "+:a+")
266    refute_diff([:foo] = [:foo, {:a, :b, :c}], "[:foo]", "[:foo, +{:a, :b, :c}+]")
267  end
268
269  test "improper lists" do
270    assert_diff([:a | :b] = [:a | :b], [])
271    assert_diff([:a, :b | :c] = [:a, :b | :c], [])
272
273    refute_diff([:a | :b] = [:b | :a], "[-:a- | -:b-]", "[+:b+ | +:a+]")
274    refute_diff([:a | :b] = [:a | :x], "[:a | -:b-]", "[:a | +:x+]")
275    refute_diff([:a, :b | :c] = [:a, :b | :x], "[:a, :b | -:c-]", "[:a, :b | +:x+]")
276    refute_diff([:a, :x | :c] = [:a, :b | :c], "[:a, -:x- | :c]", "[:a, +:b+ | :c]")
277    refute_diff([:x, :b | :c] = [:a, :b | :c], "[-:x-, :b | :c]", "[+:a+, :b | :c]")
278    refute_diff([:c, :a | :b] = [:a, :b | :c], "[-:c-, :a | -:b-]", "[:a, +:b+ | +:c+]")
279
280    refute_diff(
281      [:a, :c, :x | :b] = [:a, :b, :c | :d],
282      "[:a, :c, -:x- | -:b-]",
283      "[:a, +:b+, :c | +:d+]"
284    )
285
286    refute_diff([:a | :d] = [:a, :b, :c | :d], "[:a | -:d-]", "[:a, +:b+, +:c+ | +:d+]")
287
288    refute_diff(
289      [[:a | :x], :x | :d] = [[:a | :b], :c | :d],
290      "[[:a | -:x-], -:x- | :d]",
291      "[[:a | +:b+], +:c+ | :d]"
292    )
293
294    assert_diff([:a | x] = [:a | :b], x: :b)
295  end
296
297  test "proper lists" do
298    assert_diff([:a | [:b]] = [:a, :b], [])
299    assert_diff([:a | [:b, :c]] = [:a, :b, :c], [])
300
301    refute_diff([:a | [:b]] = [:a, :x], "[:a | [-:b-]]", "[:a, +:x+]")
302
303    refute_diff([:a, :b | [:c]] = [:a, :b, :x], "[:a, :b | [-:c-]]", "[:a, :b, +:x+]")
304    refute_diff([:a, :x | [:c]] = [:a, :b, :c], "[:a, -:x- | [:c]]", "[:a, +:b+, :c]")
305    refute_diff([:a | [:b, :c]] = [:a, :b, :x], "[:a | [:b, -:c-]]", "[:a, :b, +:x+]")
306    refute_diff([:a | [:b, :c]] = [:x, :b, :c], "[-:a- | [:b, :c]]", "[+:x+, :b, :c]")
307
308    refute_diff(
309      [:a, :c, :x | [:b, :c]] = [:a, :b, :c, :d, :e],
310      "[:a, -:c-, -:x- | [:b, :c]]",
311      "[:a, :b, :c, +:d+, +:e+]"
312    )
313
314    refute_diff([:a, :b | [:c]] = [:a, :b], "[:a, :b | [-:c-]]", "[:a, :b]")
315    refute_diff([:a, :b | []] = [:a, :b, :c], "[:a, :b | -[]-]", "[:a, :b, +:c+]")
316    refute_diff([:a, :b | [:c, :d]] = [:a, :b, :c], "[:a, :b | [:c, -:d-]]", "[:a, :b, :c]")
317    refute_diff([:a, :b | [:c, :d]] = [:a], "[:a, -:b- | [-:c-, -:d-]]", "[:a]")
318
319    refute_diff(
320      [:a, [:b, :c] | [:d, :e]] = [:a, [:x, :y], :d, :e],
321      "[:a, [-:b-, -:c-] | [:d, :e]]",
322      "[:a, [+:x+, +:y+], :d, :e]"
323    )
324
325    refute_diff(
326      [:a, [:b, :c] | [:d, :e]] = [:a, [:x, :c], :d, :e],
327      "[:a, [-:b-, :c] | [:d, :e]]",
328      "[:a, [+:x+, :c], :d, :e]"
329    )
330
331    assert_diff([:a | x] = [:a, :b], x: [:b])
332    assert_diff([:a | x] = [:a, :b, :c], x: [:b, :c])
333    assert_diff([:a | x] = [:a, :b | :c], x: [:b | :c])
334
335    pins = %{{:list_bc, nil} => [:b, :c]}
336    assert_diff([:a | ^list_bc] = [:a, :b, :c], [], pins)
337    refute_diff([:a | ^list_bc] = [:x, :x, :c], "[-:a- | -^list_bc-]", "[+:x+, +:x+, :c]", pins)
338    refute_diff([:a | ^list_bc] = [:a, :x, :c], "[:a | -^list_bc-]", "[:a, +:x+, :c]", pins)
339  end
340
341  test "concat lists" do
342    assert_diff([:a] ++ [:b] = [:a, :b], [])
343    assert_diff([:a, :b] ++ [] = [:a, :b], [])
344    assert_diff([] ++ [:a, :b] = [:a, :b], [])
345
346    refute_diff([:a, :b] ++ [:c] = [:a, :b], "[:a, :b] ++ [-:c-]", "[:a, :b]")
347    refute_diff([:a, :c] ++ [:b] = [:a, :b], "[:a, -:c-] ++ [:b]", "[:a, :b]")
348    refute_diff([:a] ++ [:b] ++ [:c] = [:a, :b], "[:a] ++ [:b] ++ [-:c-]", "[:a, :b]")
349
350    assert_diff([:a] ++ :b = [:a | :b], [])
351    assert_diff([:a] ++ x = [:a, :b], x: [:b])
352
353    refute_diff([:a, :b] ++ :c = [:a, :b, :c], "[:a, :b] ++ -:c-", "[:a, :b, +:c+]")
354    refute_diff([:a] ++ [:b] ++ :c = [:a, :b, :c], "[:a] ++ [:b] ++ -:c-", "[:a, :b, +:c+]")
355    refute_diff([:a] ++ [:b] = :a, "-[:a] ++ [:b]-", "+:a+")
356  end
357
358  @a [:a]
359  test "concat lists with module attributes" do
360    assert_diff(@a ++ [:b] = [:a, :b], [])
361    refute_diff(@a ++ [:b] = [:a], "[:a] ++ [-:b-]", "[:a]")
362    refute_diff(@a ++ [:b] = [:b], "[-:a-] ++ [:b]", "[:b]")
363  end
364
365  test "mixed lists" do
366    refute_diff([:a | :b] = [:a, :b], "[:a | -:b-]", "[:a, +:b+]")
367    refute_diff([:a, :b] = [:a | :b], "[:a, -:b-]", "[:a | +:b+]")
368    refute_diff([:a | [:b]] = [:a | :b], "[:a | -[:b]-]", "[:a | +:b+]")
369    refute_diff([:a | [:b | [:c]]] = [:a | :c], "[:a | -[:b | [:c]]-]", "[:a | +:c+]")
370    refute_diff([:a | :b] = [:a, :b, :c], "[:a | -:b-]", "[:a, +:b+, +:c+]")
371    refute_diff([:a, :b, :c] = [:a | :b], "[:a, -:b-, -:c-]", "[:a | +:b+]")
372
373    refute_diff([:a | [:b] ++ [:c]] = [:a, :b], "[:a | [:b] ++ [-:c-]]", "[:a, :b]")
374
375    refute_diff(
376      [:a | [:b] ++ [:c]] ++ [:d | :e] = [:a, :b | :e],
377      "[:a | [:b] ++ [-:c-]] ++ [-:d- | :e]",
378      "[:a, :b | :e]"
379    )
380  end
381
382  test "lists outside of match context" do
383    refute_diff(
384      [%_{i_will: :fail}, %_{i_will: :fail_too}] = [],
385      "[-%_{i_will: :fail}-, -%_{i_will: :fail_too}-]",
386      "[]"
387    )
388
389    refute_diff(
390      [:a, {:|, [], [:b, :c]}] == [:a, :b | :c],
391      "[:a, -{:|, [], [:b, :c]}-]",
392      "[:a, +:b+ | +:c+]"
393    )
394
395    refute_diff([:foo] == [:foo, {:a, :b, :c}], "[:foo]", "[:foo, +{:a, :b, :c}+]")
396    refute_diff([:foo, {:a, :b, :c}] == [:foo], "[:foo, -{:a, :b, :c}-]", "[:foo]")
397
398    refute_diff([:foo] == [:foo | {:a, :b, :c}], "[:foo]", "[:foo | +{:a, :b, :c}+]")
399    refute_diff([:foo | {:a, :b, :c}] == [:foo], "[:foo | -{:a, :b, :c}-]", "[:foo]")
400    refute_diff([:foo] == [{:a, :b, :c} | :foo], "[-:foo-]", "[+{:a, :b, :c}+ | +:foo+]")
401    refute_diff([{:a, :b, :c} | :foo] == [:foo], "[-{:a, :b, :c}- | -:foo-]", "[+:foo+]")
402  end
403
404  test "keyword lists" do
405    assert_diff([file: "nofile", line: 1] = [file: "nofile", line: 1], [])
406
407    refute_diff(
408      [file: "nofile", line: 1] = [file: nil, lime: 1],
409      ~s/[file: -"nofile"-, -line:- 1]/,
410      "[file: +nil+, +lime:+ 1]"
411    )
412
413    refute_diff(
414      [file: nil, line: 1] = [file: "nofile"],
415      "[file: -nil-, -line: 1-]",
416      ~s/[file: +"nofile"+]/
417    )
418
419    refute_diff(
420      ["foo-bar": 1] = [],
421      ~s/[-"foo-bar": 1-]/,
422      "[]"
423    )
424
425    refute_diff(
426      [file: nil] = [{:line, 1}, {1, :foo}],
427      "[-file:- -nil-]",
428      "[{+:line+, +1+}, +{1, :foo}+]"
429    )
430  end
431
432  test "tuples" do
433    assert_diff({:a, :b} = {:a, :b}, [])
434
435    refute_diff({:a, :b} = {:a, :x}, "{:a, -:b-}", "{:a, +:x+}")
436    refute_diff({:a, :b} = {:x, :x}, "{-:a-, -:b-}", "{+:x+, +:x+}")
437    refute_diff({:a, :b, :c} = {:a, :b, :x}, "{:a, :b, -:c-}", "{:a, :b, +:x+}")
438
439    refute_diff({:a} = {:a, :b}, "{:a}", "{:a, +:b+}")
440    refute_diff({:a, :b} = {:a}, "{:a, -:b-}", "{:a}")
441
442    refute_diff({:ok, value} = {:error, :fatal}, "{-:ok-, value}", "{+:error+, :fatal}")
443    refute_diff({:a, :b} = :a, "-{:a, :b}-", "+:a+")
444
445    refute_diff({:foo} = {:foo, {:a, :b, :c}}, "{:foo}", "{:foo, +{:a, :b, :c}+}")
446  end
447
448  test "tuples outside of match context" do
449    assert_diff({:a, :b} == {:a, :b}, [])
450
451    refute_diff({:a} == {:a, :b}, "{:a}", "{:a, +:b+}")
452    refute_diff({:a, :b} == {:a}, "{:a, -:b-}", "{:a}")
453
454    refute_diff({:{}, [], [:a]} == {:a}, "{-:{}-, -[]-, -[:a]-}", "{+:a+}")
455    refute_diff({:{}, [], [:a]} == :a, "-{:{}, [], [:a]}-", "+:a+")
456    refute_diff({:a, :b, :c} == {:a, :b, :x}, "{:a, :b, -:c-}", "{:a, :b, +:x+}")
457
458    refute_diff({:foo} == {:foo, {:a, :b, :c}}, "{:foo}", "{:foo, +{:a, :b, :c}+}")
459    refute_diff({:foo, {:a, :b, :c}} == {:foo}, "{:foo, -{:a, :b, :c}-}", "{:foo}")
460  end
461
462  test "maps" do
463    assert_diff(%{a: 1} = %{a: 1}, [])
464    assert_diff(%{a: 1} = %{a: 1, b: 2}, [])
465    assert_diff(%{a: 1, b: 2} = %{a: 1, b: 2}, [])
466    assert_diff(%{b: 2, a: 1} = %{a: 1, b: 2}, [])
467    assert_diff(%{a: 1, b: 2, c: 3} = %{a: 1, b: 2, c: 3}, [])
468    assert_diff(%{c: 3, b: 2, a: 1} = %{a: 1, b: 2, c: 3}, [])
469
470    refute_diff(%{a: 1, b: 2} = %{a: 1}, "%{a: 1, -b: 2-}", "%{a: 1}")
471    refute_diff(%{a: 1, b: 2} = %{a: 1, b: 12}, "%{a: 1, b: 2}", "%{a: 1, b: +1+2}")
472    refute_diff(%{a: 1, b: 2} = %{a: 1, c: 2}, "%{a: 1, -b: 2-}", "%{a: 1, c: 2}")
473    refute_diff(%{a: 1, b: 2, c: 3} = %{a: 1, b: 12}, "%{a: 1, b: 2, -c: 3-}", "%{a: 1, b: +1+2}")
474    refute_diff(%{a: 1, b: 2, c: 3} = %{a: 1, c: 2}, "%{a: 1, c: -3-, -b: 2-}", "%{a: 1, c: +2+}")
475    refute_diff(%{a: 1} = %{a: 2, b: 2, c: 3}, "%{a: -1-}", "%{a: +2+, b: 2, c: 3}")
476
477    refute_diff(
478      %{1 => :a, 2 => :b} = %{1 => :a, 12 => :b},
479      "%{1 => :a, -2 => :b-}",
480      "%{1 => :a, 12 => :b}"
481    )
482
483    refute_diff(
484      %{1 => :a, 2 => :b} = %{1 => :a, :b => 2},
485      "%{1 => :a, -2 => :b-}",
486      "%{1 => :a, :b => 2}"
487    )
488
489    pins = %{{:a, nil} => :a, {:b, nil} => :b}
490    assert_diff(%{^a => 1} = %{a: 1}, [], pins)
491    assert_diff(%{^a => x} = %{a: 1}, [x: 1], pins)
492
493    refute_diff(%{^a => 1, :a => 2} = %{a: 1}, "%{^a => 1, -:a => 2-}", "%{a: 1}", pins)
494
495    refute_diff(
496      %{^a => x, ^b => x} = %{a: 1, b: 2},
497      "%{^a => x, ^b => -x-}",
498      "%{a: 1, b: +2+}",
499      pins
500    )
501
502    refute_diff(%{a: 1} = :a, "-%{a: 1}-", "+:a+")
503  end
504
505  test "maps as pinned map value" do
506    user = %{"id" => 13, "name" => "john"}
507
508    notification = %{
509      "user" => user,
510      "subtitle" => "foo"
511    }
512
513    assert_diff(
514      %{
515        "user" => ^user,
516        "subtitle" => "foo"
517      } = notification,
518      [],
519      %{{:user, nil} => user}
520    )
521
522    refute_diff(
523      %{
524        "user" => ^user,
525        "subtitle" => "bar"
526      } = notification,
527      ~s|%{"subtitle" => "-bar-", "user" => ^user}|,
528      ~s|%{"subtitle" => "+foo+", "user" => %{"id" => 13, "name" => "john"}}|,
529      %{{:user, nil} => user}
530    )
531  end
532
533  test "maps outside match context" do
534    assert_diff(%{a: 1} == %{a: 1}, [])
535    assert_diff(%{a: 1, b: 2} == %{a: 1, b: 2}, [])
536    assert_diff(%{b: 2, a: 1} == %{a: 1, b: 2}, [])
537    assert_diff(%{a: 1, b: 2, c: 3} == %{a: 1, b: 2, c: 3}, [])
538    assert_diff(%{c: 3, b: 2, a: 1} == %{a: 1, b: 2, c: 3}, [])
539
540    refute_diff(%{a: 1} == %{a: 1, b: 2}, "%{a: 1}", "%{a: 1, +b: 2+}")
541    refute_diff(%{a: 1, b: 2} == %{a: 1}, "%{a: 1, -b: 2-}", "%{a: 1}")
542    refute_diff(%{a: 1, b: 12} == %{a: 1, b: 2}, "%{a: 1, b: -1-2}", "%{a: 1, b: 2}")
543    refute_diff(%{a: 1, b: 2} == %{a: 1, b: 12}, "%{a: 1, b: 2}", "%{a: 1, b: +1+2}")
544    refute_diff(%{a: 1, b: 2} == %{a: 1, c: 2}, "%{a: 1, -b: 2-}", "%{a: 1, +c: 2+}")
545  end
546
547  test "structs" do
548    assert_diff(%User{age: 16} = %User{age: 16}, [])
549    assert_diff(%{age: 16, __struct__: User} = %User{age: 16}, [])
550
551    refute_diff(
552      %User{age: 16} = %User{age: 21},
553      "%ExUnit.DiffTest.User{age: 1-6-}",
554      "%ExUnit.DiffTest.User{age: +2+1, name: nil}"
555    )
556
557    refute_diff(
558      %User{age: 16} = %Person{age: 21},
559      "%-ExUnit.DiffTest.User-{age: 1-6-}",
560      "%+ExUnit.DiffTest.Person+{age: +2+1}"
561    )
562
563    refute_diff(
564      %User{age: 16} = %Person{age: 21},
565      "%-ExUnit.DiffTest.User-{age: 1-6-}",
566      "%+ExUnit.DiffTest.Person+{age: +2+1}"
567    )
568
569    refute_diff(
570      %User{age: 16} = %{age: 16},
571      "%-ExUnit.DiffTest.User-{age: 16}",
572      "%{age: 16}"
573    )
574
575    refute_diff(
576      %User{age: 16} = %{age: 21},
577      "%-ExUnit.DiffTest.User-{age: 1-6-}",
578      "%{age: +2+1}"
579    )
580
581    refute_diff(
582      %{age: 16, __struct__: Person} = %User{age: 16},
583      "%-ExUnit.DiffTest.Person-{age: 16}",
584      "%+ExUnit.DiffTest.User+{age: 16, name: nil}"
585    )
586
587    pins = %{{:twenty_one, nil} => 21}
588    assert_diff(%User{age: ^twenty_one} = %User{age: 21}, [], pins)
589    assert_diff(%User{age: age} = %User{age: 21}, [age: 21], pins)
590    refute_diff(%User{age: 21} = :a, "-%ExUnit.DiffTest.User{age: 21}-", "+:a+", pins)
591  end
592
593  test "structs outside of match context" do
594    assert_diff(%User{age: 16} == %User{age: 16}, [])
595    assert_diff(%{age: 16, __struct__: User, name: nil} == %User{age: 16}, [])
596
597    refute_diff(
598      %User{age: 16} == %{age: 16},
599      "%-ExUnit.DiffTest.User-{age: 16, -name: nil-}",
600      "%{age: 16}"
601    )
602
603    refute_diff(
604      %User{age: 16} == %User{age: 21},
605      "%ExUnit.DiffTest.User{age: 1-6-, name: nil}",
606      "%ExUnit.DiffTest.User{age: +2+1, name: nil}"
607    )
608
609    refute_diff(
610      %User{age: 16} == %Person{age: 21},
611      "%-ExUnit.DiffTest.User-{age: 1-6-, -name: nil-}",
612      "%+ExUnit.DiffTest.Person+{age: +2+1}"
613    )
614  end
615
616  test "structs with inspect" do
617    refute_diff(
618      ~D[2017-10-01] = ~D[2017-10-02],
619      ~s/-~D[2017-10-01]-/,
620      "~D[2017-10-0+2+]"
621    )
622
623    refute_diff(
624      "2017-10-01" = ~D[2017-10-02],
625      ~s/-"2017-10-01"-/,
626      "+~D[2017-10-02]+"
627    )
628
629    refute_diff(
630      ~D[2017-10-02] = "2017-10-01",
631      ~s/-~D[2017-10-02]-/,
632      ~s/+"2017-10-01"+/
633    )
634  end
635
636  test "structs with missing keys on match" do
637    struct = %User{
638      age: ~U[2020-07-30 13:49:59.253158Z]
639    }
640
641    assert_diff(%User{age: %DateTime{}} = struct, [])
642
643    refute_diff(
644      %User{age: %Date{}} = struct,
645      ~s/%ExUnit.DiffTest.User{age: %-Date-{}}/,
646      ~s/%ExUnit.DiffTest.User{age: %+DateTime+{calendar: Calendar.ISO, day: 30, hour: 13, microsecond: {253158, 6}, minute: 49, month: 7, second: 59, std_offset: 0, time_zone: "Etc\/UTC", utc_offset: 0, year: 2020, zone_abbr: "UTC"}, name: nil}/
647    )
648
649    refute_diff(
650      %{age: %Date{}} = struct,
651      ~s/%{age: %-Date-{}}/,
652      ~s/%ExUnit.DiffTest.User{age: %+DateTime+{calendar: Calendar.ISO, day: 30, hour: 13, microsecond: {253158, 6}, minute: 49, month: 7, second: 59, std_offset: 0, time_zone: "Etc\/UTC", utc_offset: 0, year: 2020, zone_abbr: "UTC"}, name: nil}/
653    )
654  end
655
656  test "structs with inspect outside match context" do
657    refute_diff(
658      ~D[2017-10-01] == ~D[2017-10-02],
659      "~D[2017-10-0-1-]",
660      "~D[2017-10-0+2+]"
661    )
662
663    refute_diff(
664      "2017-10-01" == ~D[2017-10-02],
665      ~s/-"2017-10-01"-/,
666      "+~D[2017-10-02]+"
667    )
668
669    refute_diff(
670      ~D[2017-10-02] == "2017-10-01",
671      ~s/-~D[2017-10-02]-/,
672      ~s/+"2017-10-01"+/
673    )
674  end
675
676  test "structs with same inspect but different inside match" do
677    refute_diff(
678      %Opaque{data: 1} = %Opaque{data: 2},
679      "%ExUnit.DiffTest.Opaque{data: -1-}",
680      "%ExUnit.DiffTest.Opaque{data: +2+}"
681    )
682
683    refute_diff(
684      %Opaque{data: %{hello: :world}} = %Opaque{data: %{hello: "world"}},
685      "#Opaque<data: %{hello: -:-world}>",
686      "#Opaque<data: %{hello: +\"+world+\"+}>"
687    )
688  end
689
690  test "structs with same inspect but different outside match" do
691    refute_diff(
692      %Opaque{data: 1} == %Opaque{data: 2},
693      "%ExUnit.DiffTest.Opaque{data: -1-}",
694      "%ExUnit.DiffTest.Opaque{data: +2+}"
695    )
696
697    refute_diff(
698      %Opaque{data: %{hello: :world}} == %Opaque{data: %{hello: "world"}},
699      "#Opaque<data: %{hello: -:-world}>",
700      "#Opaque<data: %{hello: +\"+world+\"+}>"
701    )
702  end
703
704  test "structs with inspect in a list" do
705    refute_diff(
706      Enum.sort([~D[2019-03-31], ~D[2019-04-01]]) == [~D[2019-03-31], ~D[2019-04-01]],
707      "[-~D[2019-04-01]-, ~D[2019-03-31]]",
708      "[~D[2019-03-31], +~D[2019-04-01]+]"
709    )
710  end
711
712  test "structs with matched type" do
713    pins = %{{:type, nil} => User, {:age, nil} => 33}
714
715    # pin on __struct__
716    assert_diff(
717      %{__struct__: ^type, age: ^age, name: "john"} = %User{name: "john", age: 33},
718      [],
719      pins
720    )
721
722    refute_diff(
723      %{__struct__: ^type, age: ^age, name: "john"} = %User{name: "jane", age: 33},
724      "%{__struct__: ^type, age: ^age, name: \"j-oh-n\"}",
725      "%ExUnit.DiffTest.User{age: 33, name: \"j+a+n+e+\"}",
726      pins
727    )
728
729    refute_diff(
730      %{__struct__: ^type, age: ^age, name: "john"} = %User{name: "john", age: 35},
731      "%{__struct__: ^type, age: -^age-, name: \"john\"}",
732      "%ExUnit.DiffTest.User{age: 3+5+, name: \"john\"}",
733      pins
734    )
735
736    refute_diff(
737      %{__struct__: ^type, age: ^age, name: "john"} = ~D[2020-01-01],
738      "%{__struct__: -^type-, -age: ^age-, -name: \"john\"-}",
739      "%+Date+{calendar: Calendar.ISO, day: 1, month: 1, year: 2020}",
740      pins
741    )
742
743    # pin on %
744    assert_diff(
745      %^type{age: ^age, name: "john"} = %User{name: "john", age: 33},
746      [],
747      pins
748    )
749
750    refute_diff(
751      %^type{age: ^age, name: "john"} = %User{name: "jane", age: 33},
752      "%{__struct__: ^type, age: ^age, name: \"j-oh-n\"}",
753      "%ExUnit.DiffTest.User{age: 33, name: \"j+a+n+e+\"}",
754      pins
755    )
756
757    refute_diff(
758      %^type{age: ^age, name: "john"} = %User{name: "john", age: 35},
759      "%{__struct__: ^type, age: -^age-, name: \"john\"}",
760      "%ExUnit.DiffTest.User{age: 3+5+, name: \"john\"}",
761      pins
762    )
763
764    refute_diff(
765      %^type{age: ^age, name: "john"} = ~D[2020-01-01],
766      "%{__struct__: -^type-, -age: ^age-, -name: \"john\"-}",
767      "%+Date+{calendar: Calendar.ISO, day: 1, month: 1, year: 2020}",
768      pins
769    )
770  end
771
772  test "invalid structs" do
773    refute_diff(
774      %{__struct__: Unknown} = %{},
775      "%{-__struct__: Unknown-}",
776      "%{}"
777    )
778
779    refute_diff(
780      %{__struct__: Date, unknown: :field} = %{},
781      "%{-__struct__: Date-, -unknown: :field-}",
782      "%{}"
783    )
784  end
785
786  test "maps and structs with escaped values" do
787    refute_diff(
788      %User{age: {1, 2, 3}} = %User{age: {1, 2, 4}},
789      "%ExUnit.DiffTest.User{age: {1, 2, -3-}}",
790      "%ExUnit.DiffTest.User{age: {1, 2, +4+}, name: nil}"
791    )
792
793    refute_diff(
794      %User{age: {1, 2, 3}, name: name} = %User{age: {1, 2, 4}},
795      "%ExUnit.DiffTest.User{age: {1, 2, -3-}, name: name}",
796      "%ExUnit.DiffTest.User{age: {1, 2, +4+}, name: nil}"
797    )
798
799    refute_diff(
800      %User{name: :foo} = %User{name: :bar, age: {1, 2, 3}},
801      "%ExUnit.DiffTest.User{name: -:foo-}",
802      "%ExUnit.DiffTest.User{name: +:bar+, age: {1, 2, 3}}"
803    )
804
805    refute_diff(
806      %User{age: {1, 2, 3}} == %User{age: {1, 2, 4}},
807      "%ExUnit.DiffTest.User{age: {1, 2, -3-}, name: nil}",
808      "%ExUnit.DiffTest.User{age: {1, 2, +4+}, name: nil}"
809    )
810
811    refute_diff(
812      %User{age: {1, 2, 4}} == %User{age: {1, 2, 3}},
813      "%ExUnit.DiffTest.User{age: {1, 2, -4-}, name: nil}",
814      "%ExUnit.DiffTest.User{age: {1, 2, +3+}, name: nil}"
815    )
816
817    refute_diff(
818      %{name: :foo} == %{name: :foo, age: {1, 2, 3}},
819      "%{name: :foo}",
820      "%{name: :foo, +age: {1, 2, 3}+}"
821    )
822
823    refute_diff(
824      %{name: :foo, age: {1, 2, 3}} == %{name: :foo},
825      "%{name: :foo, -age: {1, 2, 3}-}",
826      "%{name: :foo}"
827    )
828  end
829
830  test "strings" do
831    assert_diff("" = "", [])
832    assert_diff("fox hops over the dog" = "fox hops over the dog", [])
833
834    refute_diff("fox" = "foo", "fo-x-", "fo+o+")
835
836    refute_diff(
837      "fox hops over \"the dog" = "fox  jumps over the  lazy cat",
838      ~s/"fox -ho-ps over -\\\"-the -dog-"/,
839      ~s/"fox + jum+ps over the + lazy cat+"/
840    )
841
842    refute_diff(
843      "short" = "really long string that should not emit diff against short",
844      ~s/"-short-"/,
845      ~s/"+really long string that should not emit diff against short+"/
846    )
847
848    refute_diff("foo" = :a, ~s/-"foo"-/, "+:a+")
849  end
850
851  test "strings outside match context" do
852    assert_diff("" == "", [])
853    assert_diff("fox hops over the dog" == "fox hops over the dog", [])
854    refute_diff("fox" == "foo", "fo-x-", "fo+o+")
855
856    refute_diff(
857      "{\"foo\":1,\"barbaz\":[1,2,3]}" == "4",
858      ~s/"-{\\\"foo\\\":1,\\\"barbaz\\\":[1,2,3]}-"/,
859      ~s/"+4+"/
860    )
861  end
862
863  test "concat binaries" do
864    assert_diff("fox hops" <> " over the dog" = "fox hops over the dog", [])
865    assert_diff("fox hops " <> "over " <> "the dog" = "fox hops over the dog", [])
866
867    refute_diff(
868      "fox hops" <> " under the dog" = "fox hops over the dog",
869      ~s/"fox hops" <> " -und-er the dog"/,
870      ~s/"fox hops +ov+er the dog"/
871    )
872
873    refute_diff(
874      "fox hops over" <> " the dog" = "fox hops over",
875      ~s/"fox hops over" <> "- the dog-"/,
876      ~s/"fox hops over"/
877    )
878
879    refute_diff(
880      "fox hops" <> " over the dog" = "fox",
881      ~s/"-fox hops-" <> "- over the dog-"/,
882      ~s/"+fox+"/
883    )
884
885    refute_diff(
886      "fox" <> " hops" = "fox h",
887      ~s/"fox" <> " h-ops-"/,
888      ~s/"fox h"/
889    )
890
891    refute_diff(
892      "fox hops " <> "hover " <> "the dog" = "fox hops over the dog",
893      ~s/"fox hops " <> "-h-over " <> "the dog"/,
894      ~s/"fox hops over the dog"/
895    )
896
897    refute_diff("fox" <> " hops" = :a, ~s/-"fox" <> " hops"-/, "+:a+")
898  end
899
900  test "concat binaries with pin" do
901    pins = %{{:x, nil} => " over the dog"}
902
903    assert_diff("fox hops" <> x = "fox hops over the dog", x: " over the dog")
904    assert_diff("fox hops " <> "over " <> x = "fox hops over the dog", x: "the dog")
905    assert_diff("fox hops" <> ^x = "fox hops over the dog", [], pins)
906
907    refute_diff(
908      "fox hops " <> "hover " <> x = "fox hops over the dog",
909      ~s/"fox hops " <> "-h-over " <> x/,
910      ~s/"fox hops over +t+he dog"/
911    )
912
913    refute_diff(
914      "fox hops " <> "hover " <> ^x = "fox hops over the dog",
915      ~s/"fox hops " <> "-h-over " <> -^x-/,
916      ~s/"fox hops over +t+he dog"/,
917      pins
918    )
919  end
920
921  test "concat binaries with specifiers" do
922    input = "foobar"
923
924    refute_diff(
925      <<trap::binary-size(3)>> <> "baz" = input,
926      "-<<trap::binary-size(3)>> <> \"baz\"-",
927      "+\"foobar\"+"
928    )
929  end
930
931  test "underscore" do
932    assert_diff(_ = :a, [])
933    assert_diff({_, _} = {:a, :b}, [])
934
935    refute_diff({_, :a} = {:b, :b}, "{_, -:a-}", "{:b, +:b+}")
936  end
937
938  test "macros" do
939    assert_diff(one() = 1, [])
940    assert_diff(tuple(x, x) = {1, 1}, x: 1)
941
942    refute_diff(one() = 2, "-one()-", "+2+")
943    refute_diff(tuple(x, x) = {1, 2}, "-tuple(x, x)-", "{1, +2+}")
944
945    pins = %{{:x, nil} => 1}
946    assert_diff(pin_x() = 1, [], pins)
947    refute_diff(pin_x() = 2, "-pin_x()-", "+2+", pins)
948  end
949
950  test "guards" do
951    assert_diff((x when x == 0) = 0, x: 0)
952    assert_diff((x when x == 0 and is_integer(x)) = 0, x: 0)
953    assert_diff((x when x == 0 or x == 1) = 0, x: 0)
954    assert_diff((x when x == 0 when x == 1) = 0, x: 0)
955    assert_diff((x when one() == 1) = 0, x: 0)
956
957    refute_diff((x when x == 1) = 0, "x when -x == 1-", "0")
958    refute_diff((x when x == 0 and x == 1) = 0, "x when x == 0 and -x == 1-", "0")
959    refute_diff((x when x == 1 and x == 2) = 0, "x when -x == 1- and -x == 2-", "0")
960    refute_diff((x when x == 1 or x == 2) = 0, "x when -x == 1- or -x == 2-", "0")
961    refute_diff((x when x == 1 when x == 2) = 0, "x when -x == 1- when -x == 2-", "0")
962    refute_diff((x when x in [1, 2]) = 0, "x when -x in [1, 2]-", "0")
963    refute_diff(({:ok, x} when x == 1) = :error, "-{:ok, x}- when x == 1", "+:error+")
964  end
965
966  test "charlists" do
967    refute_diff(
968      'fox hops over \'the dog' = 'fox jumps over the lazy cat',
969      "'fox -ho-ps over -\\'-the -dog-'",
970      "'fox +jum+ps over the +lazy cat+'"
971    )
972
973    refute_diff({[], :ok} = {[], [], :ok}, "{[], -:ok-}", "{[], +[]+, +:ok+}")
974    refute_diff({[], :ok} = {'foo', [], :ok}, "{'--', -:ok-}", "{'+foo+', +[]+, +:ok+}")
975    refute_diff({'foo', :ok} = {[], [], :ok}, "{'-foo-', -:ok-}", "{'++', +[]+, +:ok+}")
976    refute_diff({'foo', :ok} = {'bar', [], :ok}, "{'-foo-', -:ok-}", "{'+bar+', +[]+, +:ok+}")
977  end
978
979  test "refs" do
980    ref1 = make_ref()
981    ref2 = make_ref()
982
983    inspect_ref1 = inspect(ref1)
984    inspect_ref2 = inspect(ref2)
985
986    assert_diff(ref1 == ref1, [])
987    assert_diff({ref1, ref2} == {ref1, ref2}, [])
988
989    refute_diff(ref1 == ref2, "-#{inspect_ref1}-", "+#{inspect_ref2}+")
990
991    refute_diff(
992      {ref1, ref2} == {ref2, ref1},
993      "{-#{inspect_ref1}-, -#{inspect_ref2}-}",
994      "{+#{inspect_ref2}+, +#{inspect_ref1}+}"
995    )
996
997    refute_diff(
998      {ref1, ref2} == ref1,
999      "-{#{inspect_ref1}, #{inspect_ref2}}-",
1000      "+#{inspect_ref1}+"
1001    )
1002
1003    refute_diff(
1004      ref1 == {ref1, ref2},
1005      "-#{inspect_ref1}-",
1006      "+{#{inspect_ref1}, #{inspect_ref2}}+"
1007    )
1008
1009    refute_diff(ref1 == :a, "-#{inspect_ref1}-", "+:a+")
1010    refute_diff({ref1, ref2} == :a, "-{#{inspect_ref1}, #{inspect_ref2}}", "+:a+")
1011    refute_diff(%{ref1 => ref2} == :a, "-%{#{inspect_ref1} => #{inspect_ref2}}", "+:a+")
1012
1013    refute_diff(
1014      %Opaque{data: ref1} == :a,
1015      "-#Opaque<???>-",
1016      "+:a+"
1017    )
1018  end
1019
1020  test "pids" do
1021    pid = self()
1022    inspect_pid = inspect(pid)
1023
1024    assert_diff(pid == pid, [])
1025    assert_diff({pid, pid} == {pid, pid}, [])
1026
1027    refute_diff(pid == :a, "-#{inspect_pid}-", "+:a+")
1028    refute_diff({pid, pid} == :a, "-{#{inspect_pid}, #{inspect_pid}}", "+:a+")
1029
1030    refute_diff({pid, :a} == {:a, pid}, "{-#{inspect_pid}-, -:a-}", "{+:a+, +#{inspect_pid}+}")
1031    refute_diff(%{pid => pid} == :a, "-%{#{inspect_pid} => #{inspect_pid}}", "+:a+")
1032
1033    refute_diff(
1034      %Opaque{data: pid} == :a,
1035      "-#Opaque<???>-",
1036      "+:a+"
1037    )
1038  end
1039
1040  test "functions" do
1041    identity = & &1
1042    inspect = inspect(identity)
1043
1044    assert_diff(identity == identity, [])
1045    assert_diff({identity, identity} == {identity, identity}, [])
1046
1047    refute_diff(identity == :a, "-#{inspect}-", "+:a+")
1048    refute_diff({identity, identity} == :a, "-{#{inspect}, #{inspect}}", "+:a+")
1049
1050    refute_diff({identity, :a} == {:a, identity}, "{-#{inspect}-, -:a-}", "{+:a+, +#{inspect}+}")
1051
1052    refute_diff(%{identity => identity} == :a, "-%{#{inspect} => #{inspect}}", "+:a+")
1053
1054    refute_diff(
1055      %Opaque{data: identity} == :a,
1056      "-#Opaque<???>-",
1057      "+:a+"
1058    )
1059  end
1060
1061  test "not supported" do
1062    refute_diff(
1063      <<147, 1, 2, 31>> = <<193, 1, 31>>,
1064      "-<<147, 1, 2, 31>>-",
1065      "+<<193, 1, 31>>+"
1066    )
1067  end
1068end
1069