1Code.require_file("../../test_helper.exs", __DIR__) 2 3defmodule Mix.Tasks.CompileTest do 4 use MixTest.Case 5 6 if System.otp_release() >= "24" do 7 defmacro position(line, column), do: {line, column} 8 else 9 defmacro position(line, _column), do: line 10 end 11 12 defmodule CustomCompilers do 13 def project do 14 [compilers: [:elixir, :app, :custom]] 15 end 16 end 17 18 defmodule DepsApp do 19 def project do 20 [app: :deps_app, version: "0.1.0", deps: [{:ok, "0.1.0", path: "deps/ok"}]] 21 end 22 end 23 24 defmodule WrongPath do 25 def project do 26 [app: :apps_path_bug, apps_path: "this_path_does_not_exist"] 27 end 28 end 29 30 setup do 31 Mix.Project.push(MixTest.Case.Sample) 32 :ok 33 end 34 35 test "compiles --list with mixfile" do 36 Mix.Task.run("compile", ["--list"]) 37 38 msg = "\nEnabled compilers: yecc, leex, erlang, elixir, app, protocols" 39 assert_received {:mix_shell, :info, [^msg]} 40 41 assert_received {:mix_shell, :info, ["mix compile.elixir # " <> _]} 42 end 43 44 test "compiles --list with custom mixfile" do 45 Mix.Project.pop() 46 Mix.Project.push(CustomCompilers) 47 Mix.Task.run("compile", ["--list"]) 48 assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]} 49 end 50 51 test "compiles does not require all compilers available on manifest" do 52 Mix.Project.pop() 53 Mix.Project.push(CustomCompilers) 54 assert Mix.Tasks.Compile.manifests() |> Enum.map(&Path.basename/1) == ["compile.elixir"] 55 end 56 57 test "compiles a project with cached deps information" do 58 Mix.Project.pop() 59 60 in_fixture("deps_status", fn -> 61 Mix.Project.push(DepsApp) 62 63 File.mkdir_p!("lib") 64 65 File.write!("lib/a.ex", """ 66 root = File.cwd!() 67 File.cd!("lib", fn -> 68 %{ok: path} = Mix.Project.deps_paths() 69 70 if Path.relative_to(path, root) != "deps/ok" do 71 raise "non cached path" 72 end 73 end) 74 """) 75 76 assert Mix.Task.run("compile", ["--force", "--from-mix-deps-compile"]) == {:ok, []} 77 end) 78 end 79 80 test "compiles a project with mixfile" do 81 in_fixture("no_mixfile", fn -> 82 assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []} 83 assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam") 84 assert File.regular?("_build/dev/lib/sample/ebin/sample.app") 85 assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} 86 assert_received {:mix_shell, :info, ["Generated sample app"]} 87 assert File.regular?("_build/dev/lib/sample/consolidated/Elixir.Enumerable.beam") 88 89 # Noop 90 Mix.Task.clear() 91 assert Mix.Task.run("compile", ["--verbose"]) == {:noop, []} 92 refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]} 93 94 # Consolidates protocols if manifest is out of date 95 File.rm("_build/dev/lib/sample/.mix/compile.protocols") 96 Mix.Task.clear() 97 assert Mix.Task.run("compile", ["--verbose"]) == {:ok, []} 98 refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]} 99 assert File.regular?("_build/dev/lib/sample/consolidated/Elixir.Enumerable.beam") 100 101 # Purge so consolidated is picked up 102 purge([Enumerable]) 103 assert Mix.Tasks.App.Start.run(["--verbose"]) == :ok 104 assert Protocol.consolidated?(Enumerable) 105 end) 106 end 107 108 # A.info/0 is loaded dynamically 109 @compile {:no_warn_undefined, {A, :info, 0}} 110 111 test "adds Logger application metadata" do 112 import ExUnit.CaptureLog 113 114 in_fixture("no_mixfile", fn -> 115 Process.put({MixTest.Case.Sample, :application}, extra_applications: [:logger]) 116 117 File.write!("lib/a.ex", """ 118 defmodule A do 119 require Logger 120 def info, do: Logger.info("hello") 121 end 122 """) 123 124 assert Mix.Task.run("compile", []) == {:ok, []} 125 126 try do 127 assert capture_log([metadata: [:application]], &A.info/0) =~ "application=sample" 128 after 129 purge([A]) 130 end 131 end) 132 end 133 134 test "returns errors from compilers when --return-errors is set" do 135 in_fixture("no_mixfile", fn -> 136 File.write!("lib/a.ex", """ 137 defmodule A do 138 def my_fn(), do: $$$ 139 end 140 """) 141 142 file = Path.absname("lib/a.ex") 143 144 ExUnit.CaptureIO.capture_io(fn -> 145 assert {:error, [diagnostic]} = Mix.Task.run("compile", ["--return-errors"]) 146 147 assert %Mix.Task.Compiler.Diagnostic{ 148 file: ^file, 149 severity: :error, 150 position: 2, 151 message: "** (SyntaxError) lib/a.ex:2:" <> _, 152 compiler_name: "Elixir" 153 } = diagnostic 154 end) 155 end) 156 end 157 158 test "calling raise inside a macro returns a diagnostic with a position" do 159 in_fixture("no_mixfile", fn -> 160 File.write!("lib/a.ex", """ 161 defmodule A do 162 defmacro custom_macro do 163 raise "error" 164 end 165 end 166 """) 167 168 File.write!("lib/b.ex", """ 169 defmodule B do 170 require A 171 A.custom_macro() 172 end 173 """) 174 175 file = Path.absname("lib/b.ex") 176 177 ExUnit.CaptureIO.capture_io(fn -> 178 assert {:error, [diagnostic]} = Mix.Task.run("compile", ["--return-errors"]) 179 180 assert %Mix.Task.Compiler.Diagnostic{ 181 file: ^file, 182 severity: :error, 183 position: 3, 184 message: "** (RuntimeError) error\n expanding macro: A.custom_macro/0" <> _, 185 compiler_name: "Elixir" 186 } = diagnostic 187 end) 188 end) 189 end 190 191 test "returns syntax error from an Erlang file when --return-errors is set" do 192 in_fixture("no_mixfile", fn -> 193 import ExUnit.CaptureIO 194 195 file = Path.absname("src/a.erl") 196 File.mkdir!("src") 197 198 File.write!(file, """ 199 -module(b). 200 def b(), do: b 201 """) 202 203 assert File.regular?(file) 204 205 capture_io(fn -> 206 assert {:error, [diagnostic]} = Mix.Task.run("compile", ["--force", "--return-errors"]) 207 208 assert %Mix.Task.Compiler.Diagnostic{ 209 compiler_name: "erl_parse", 210 file: ^file, 211 message: "syntax error before: b", 212 position: position(2, 5), 213 severity: :error 214 } = diagnostic 215 end) 216 217 refute File.regular?("ebin/Elixir.A.beam") 218 refute File.regular?("ebin/Elixir.B.beam") 219 end) 220 end 221 222 test "skip protocol consolidation when --no-protocol-consolidation" do 223 in_fixture("no_mixfile", fn -> 224 File.rm("_build/dev/lib/sample/.mix/compile.protocols") 225 assert Mix.Task.run("compile", ["--no-protocol-consolidation"]) == {:ok, []} 226 assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam") 227 refute File.regular?("_build/dev/lib/sample/consolidated/Elixir.Enumerable.beam") 228 end) 229 end 230 231 test "loads consolidated protocols even on --no-compile" do 232 in_fixture("no_mixfile", fn -> 233 File.rm("_build/dev/lib/sample/.mix/compile.protocols") 234 consolidated = "_build/dev/lib/sample/consolidated" |> Path.expand() |> to_charlist() 235 236 assert Mix.Task.run("compile") == {:ok, []} 237 assert File.regular?("_build/dev/lib/sample/consolidated/Elixir.Enumerable.beam") 238 assert consolidated in :code.get_path() 239 240 Code.delete_path("_build/dev/lib/sample/consolidated") 241 assert Mix.Task.rerun("compile", ["--no-compile"]) == {:noop, []} 242 assert consolidated in :code.get_path() 243 end) 244 end 245 246 test "loads Mix config with --erl-config" do 247 in_fixture("no_mixfile", fn -> 248 File.write!("mix.config", "{erl_config_app, [{value, true}]}.") 249 assert Mix.Task.run("compile", ["--erl-config", "mix.config"]) == {:ok, []} 250 assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam") 251 assert Application.get_env(:erl_config_app, :value) 252 end) 253 after 254 Application.delete_env(:erl_config_app, :value) 255 end 256 257 test "runs after_compiler callback once" do 258 in_fixture("no_mixfile", fn -> 259 callback = fn result -> send(self(), result) end 260 261 assert Mix.Task.Compiler.after_compiler(:elixir, callback) == :ok 262 assert Mix.Task.rerun("compile", []) == {:ok, []} 263 assert_received {:ok, []} 264 265 Mix.Task.clear() 266 assert Mix.Task.rerun("compile", []) == {:noop, []} 267 refute_received {:noop, []} 268 269 Mix.Task.clear() 270 assert Mix.Task.Compiler.after_compiler(:elixir, callback) == :ok 271 assert Mix.Task.run("compile", []) == {:noop, []} 272 assert_received {:noop, []} 273 end) 274 end 275 276 test "does not crash on a project with bad path" do 277 Mix.Project.pop() 278 Mix.Project.push(WrongPath) 279 280 ExUnit.CaptureIO.capture_io(fn -> 281 assert Mix.Task.run("compile", ["--no-protocol-consolidation"]) == {:noop, []} 282 end) 283 end 284 285 test "validates compile_env" do 286 in_fixture("no_mixfile", fn -> 287 File.mkdir_p!("config") 288 289 File.write!("config/config.exs", """ 290 import Config 291 config :sample, :hello, System.get_env("MIX_SAMPLE_HELLO") 292 """) 293 294 File.write!("lib/a.ex", """ 295 defmodule A do 296 require Application 297 _ = Application.compile_env(:sample, :hello) 298 end 299 """) 300 301 System.put_env("MIX_SAMPLE_HELLO", "compile") 302 Mix.Tasks.Loadconfig.run([]) 303 assert Mix.Tasks.Compile.All.run([]) == {:ok, []} 304 305 System.put_env("MIX_SAMPLE_HELLO", "runtime") 306 Mix.Tasks.Loadconfig.run([]) 307 Application.unload(:sample) 308 309 assert ExUnit.CaptureIO.capture_io(:stderr, fn -> 310 assert_raise ErlangError, fn -> Mix.Tasks.Compile.All.run([]) end 311 end) =~ 312 " the application :sample has a different value set for key :hello during runtime compared to compile time" 313 314 # Can start if compile env matches 315 System.put_env("MIX_SAMPLE_HELLO", "compile") 316 Mix.Tasks.Loadconfig.run([]) 317 assert Mix.Tasks.App.Start.run([]) == :ok 318 end) 319 after 320 System.delete_env("MIX_SAMPLE_HELLO") 321 Application.delete_env(:sample, :hello, persistent: true) 322 end 323end 324