1Code.require_file("../../test_helper.exs", __DIR__)
2
3defmodule Mix.Tasks.Compile.ErlangTest do
4  use MixTest.Case
5  import ExUnit.CaptureIO
6
7  if System.otp_release() >= "24" do
8    defmacro position(line, column), do: {line, column}
9  else
10    defmacro position(line, _column), do: line
11  end
12
13  setup config do
14    erlc_options = Map.get(config, :erlc_options, [])
15    Mix.ProjectStack.post_config(erlc_options: erlc_options)
16    Mix.Project.push(MixTest.Case.Sample)
17    :ok
18  end
19
20  @tag erlc_options: [{:d, 'foo', 'bar'}]
21  test "raises on invalid erlc_options" do
22    in_fixture("compile_erlang", fn ->
23      assert_raise Mix.Error, ~r"Compiling Erlang file '.*' failed", fn ->
24        capture_io(fn ->
25          Mix.Tasks.Compile.Erlang.run([])
26        end)
27      end
28    end)
29  end
30
31  test "compiles and cleans src/b.erl and src/c.erl" do
32    in_fixture("compile_erlang", fn ->
33      assert Mix.Tasks.Compile.Erlang.run(["--verbose"]) == {:ok, []}
34      assert_received {:mix_shell, :info, ["Compiled src/b.erl"]}
35      assert_received {:mix_shell, :info, ["Compiled src/c.erl"]}
36
37      assert File.regular?("_build/dev/lib/sample/ebin/b.beam")
38      assert File.regular?("_build/dev/lib/sample/ebin/c.beam")
39
40      assert Mix.Tasks.Compile.Erlang.run(["--verbose"]) == {:noop, []}
41      refute_received {:mix_shell, :info, ["Compiled src/b.erl"]}
42
43      assert Mix.Tasks.Compile.Erlang.run(["--force", "--verbose"]) == {:ok, []}
44      assert_received {:mix_shell, :info, ["Compiled src/b.erl"]}
45      assert_received {:mix_shell, :info, ["Compiled src/c.erl"]}
46
47      assert Mix.Tasks.Compile.Erlang.clean()
48      refute File.regular?("_build/dev/lib/sample/ebin/b.beam")
49      refute File.regular?("_build/dev/lib/sample/ebin/c.beam")
50    end)
51  end
52
53  test "removes old artifact files" do
54    in_fixture("compile_erlang", fn ->
55      assert Mix.Tasks.Compile.Erlang.run([]) == {:ok, []}
56      assert File.regular?("_build/dev/lib/sample/ebin/b.beam")
57
58      File.rm!("src/b.erl")
59      assert Mix.Tasks.Compile.Erlang.run([]) == {:ok, []}
60      refute File.regular?("_build/dev/lib/sample/ebin/b.beam")
61    end)
62  end
63
64  test "compilation purges the module" do
65    in_fixture("compile_erlang", fn ->
66      # Create the first version of the module.
67      defmodule :purge_test do
68        def version, do: :v1
69      end
70
71      assert :v1 == :purge_test.version()
72
73      # Create the second version of the module (this time as Erlang source).
74      File.write!("src/purge_test.erl", """
75      -module(purge_test).
76      -export([version/0]).
77      version() -> v2.
78      """)
79
80      assert Mix.Tasks.Compile.Erlang.run([]) == {:ok, []}
81
82      # If the module was not purged on recompilation, this would fail.
83      assert :v2 == :purge_test.version()
84    end)
85  end
86
87  test "continues even if one file fails to compile" do
88    in_fixture("compile_erlang", fn ->
89      file = Path.absname("src/zzz.erl")
90
91      File.write!(file, """
92      -module(zzz).
93      def zzz(), do: b
94      """)
95
96      capture_io(fn ->
97        assert {:error, [diagnostic]} = Mix.Tasks.Compile.Erlang.run([])
98
99        assert %Mix.Task.Compiler.Diagnostic{
100                 compiler_name: "erl_parse",
101                 file: ^file,
102                 message: "syntax error before: zzz",
103                 position: position(2, 5),
104                 severity: :error
105               } = diagnostic
106      end)
107
108      assert File.regular?("_build/dev/lib/sample/ebin/b.beam")
109      assert File.regular?("_build/dev/lib/sample/ebin/c.beam")
110    end)
111  end
112
113  test "saves warnings between builds" do
114    in_fixture("compile_erlang", fn ->
115      file = Path.absname("src/has_warning.erl")
116
117      File.write!(file, """
118      -module(has_warning).
119      my_fn() -> ok.
120      """)
121
122      capture_io(fn ->
123        assert {:ok, [diagnostic]} = Mix.Tasks.Compile.Erlang.run([])
124
125        assert %Mix.Task.Compiler.Diagnostic{
126                 file: ^file,
127                 compiler_name: "erl_lint",
128                 message: "function my_fn/0 is unused",
129                 position: position(2, 1),
130                 severity: :warning
131               } = diagnostic
132
133        # Should return warning without recompiling file
134        assert {:noop, [^diagnostic]} = Mix.Tasks.Compile.Erlang.run(["--verbose"])
135        refute_received {:mix_shell, :info, ["Compiled src/has_warning.erl"]}
136
137        # Should not return warning after changing file
138        File.write!(file, """
139        -module(has_warning).
140        -export([my_fn/0]).
141        my_fn() -> ok.
142        """)
143
144        ensure_touched(file)
145        assert {:ok, []} = Mix.Tasks.Compile.Erlang.run([])
146      end)
147    end)
148  end
149
150  test "prints warnings from stale files with --all-warnings" do
151    in_fixture("compile_erlang", fn ->
152      file = Path.absname("src/has_warning.erl")
153
154      File.write!(file, """
155      -module(has_warning).
156      my_fn() -> ok.
157      """)
158
159      capture_io(fn -> Mix.Tasks.Compile.Erlang.run([]) end)
160
161      output =
162        capture_io(fn ->
163          assert {:noop, _} = Mix.Tasks.Compile.Erlang.run(["--all-warnings"])
164        end)
165
166      assert output =~ ~r"src/has_warning.erl:2:(1:)? warning: function my_fn/0 is unused\n"
167
168      # Should not print old warnings after fixing
169      File.write!(file, """
170      -module(has_warning).
171      """)
172
173      ensure_touched(file)
174
175      output =
176        capture_io(fn ->
177          Mix.Tasks.Compile.Erlang.run(["--all-warnings"])
178        end)
179
180      assert output == ""
181    end)
182  end
183
184  @tag erlc_options: [{:warnings_as_errors, true}]
185  test "adds :debug_info to erlc_options by default" do
186    in_fixture("compile_erlang", fn ->
187      Mix.Tasks.Compile.Erlang.run([])
188
189      binary = File.read!("_build/dev/lib/sample/ebin/b.beam")
190
191      {:ok, {_, [debug_info: {:debug_info_v1, _, {debug_info, _}}]}} =
192        :beam_lib.chunks(binary, [:debug_info])
193
194      assert debug_info != :none
195    end)
196  end
197end
198