1Code.require_file("../../test_helper.exs", __DIR__)
2
3defmodule Mix.Tasks.EscriptTest do
4  use MixTest.Case
5
6  defmodule Escript do
7    def project do
8      [
9        app: :escript_test,
10        version: "0.0.1",
11        escript: [main_module: EscriptTest, name: "escript_test", embed_elixir: true]
12      ]
13    end
14  end
15
16  defmodule EscriptWithDebugInfo do
17    def project do
18      [
19        app: :escript_test_with_debug_info,
20        version: "0.0.1",
21        escript: [main_module: EscriptTest, strip_beams: false]
22      ]
23    end
24  end
25
26  defmodule EscriptWithPath do
27    def project do
28      [
29        app: :escript_test_with_path,
30        version: "0.0.1",
31        escript: [
32          app: nil,
33          embed_elixir: true,
34          main_module: EscriptTest,
35          path: Path.join("ebin", "escript_test_with_path")
36        ]
37      ]
38    end
39  end
40
41  defmodule EscriptWithDeps do
42    def project do
43      [
44        app: :escript_test_with_deps,
45        version: "0.0.1",
46        escript: [main_module: EscriptTest],
47        deps: [{:ok, path: fixture_path("deps_status/deps/ok")}]
48      ]
49    end
50  end
51
52  defmodule EscriptErlangWithDeps do
53    def project do
54      [
55        app: :escript_test_erlang_with_deps,
56        version: "0.0.1",
57        language: :erlang,
58        escript: [main_module: :escript_test],
59        deps: [{:ok, path: fixture_path("deps_status/deps/ok")}]
60      ]
61    end
62
63    def application do
64      [applications: [], extra_applications: [:crypto, elixir: :optional]]
65    end
66  end
67
68  defmodule EscriptErlangMainModule do
69    def project do
70      [
71        app: :escript_test_erlang_main_module,
72        version: "0.0.1",
73        escript: [main_module: :escript_test]
74      ]
75    end
76  end
77
78  defmodule EscriptWithUnknownMainModule do
79    def project do
80      [
81        app: :escript_test_with_unknown_main_module,
82        version: "0.0.1",
83        escript: [main_module: BogusEscriptTest]
84      ]
85    end
86  end
87
88  defmodule EscriptConsolidated do
89    def project do
90      [
91        app: :escript_test_consolidated,
92        build_embedded: true,
93        version: "0.0.1",
94        escript: [main_module: EscriptTest]
95      ]
96    end
97  end
98
99  test "generate escript" do
100    in_fixture("escript_test", fn ->
101      Mix.Project.push(Escript)
102
103      Mix.Tasks.Escript.Build.run([])
104      assert_received {:mix_shell, :info, ["Generated escript escript_test with MIX_ENV=dev"]}
105      assert System.cmd("escript", ["escript_test"]) == {"TEST\n", 0}
106      assert count_abstract_code("escript_test") == 0
107    end)
108  end
109
110  test "generate escript with --no-compile option" do
111    in_fixture("escript_test", fn ->
112      Mix.Project.push(Escript)
113
114      Mix.Tasks.Compile.run([])
115      purge([EscriptTest])
116
117      Mix.Tasks.Escript.Build.run(["--no-compile"])
118      assert_received {:mix_shell, :info, ["Generated escript escript_test with MIX_ENV=dev"]}
119    end)
120  end
121
122  test "generate escript with compile config" do
123    in_fixture("escript_test", fn ->
124      Mix.Project.push(Escript)
125
126      File.mkdir_p!("config")
127
128      File.write!("config/config.exs", ~S"""
129      import Config
130      config :foobar, :value, "COMPILE #{config_env()} TARGET #{config_target()}"
131      """)
132
133      Mix.Tasks.Escript.Build.run([])
134      assert_received {:mix_shell, :info, ["Generated escript escript_test with MIX_ENV=dev"]}
135      assert System.cmd("escript", ["escript_test"]) == {"COMPILE dev TARGET host\n", 0}
136    end)
137  end
138
139  test "generate escript with runtime config" do
140    in_fixture("escript_test", fn ->
141      Mix.Project.push(Escript)
142
143      File.mkdir_p!("config")
144
145      File.write!("config/config.exs", """
146      [foobar: [value: "OLD", nesting: [config: true, runtime: false]]]
147      """)
148
149      File.write!("config/runtime.exs", ~S"""
150      import Config
151      config :foobar, :value, "RUNTIME #{config_env()} TARGET #{config_target()} ARGV #{System.argv()}"
152      config :foobar, :nesting, runtime: true
153      """)
154
155      Mix.Tasks.Escript.Build.run([])
156      assert_received {:mix_shell, :info, ["Generated escript escript_test with MIX_ENV=dev"]}
157
158      assert System.cmd("escript", ["escript_test", "--foo", "--bar"]) ==
159               {"RUNTIME dev TARGET host ARGV --foo--bar\n", 0}
160
161      assert System.cmd("escript", ["escript_test", "--nesting"]) ==
162               {"[config: true, runtime: true]\n", 0}
163    end)
164  end
165
166  test "generate escript with debug information" do
167    in_fixture("escript_test", fn ->
168      Mix.Project.push(EscriptWithDebugInfo)
169
170      Mix.Tasks.Escript.Build.run([])
171
172      msg = "Generated escript escript_test_with_debug_info with MIX_ENV=dev"
173      assert_received {:mix_shell, :info, [^msg]}
174
175      assert System.cmd("escript", ["escript_test_with_debug_info"]) == {"TEST\n", 0}
176      assert count_abstract_code("escript_test_with_debug_info") > 0
177    end)
178  end
179
180  defp count_abstract_code(escript_filename) do
181    escript_filename
182    |> read_beams()
183    |> Enum.count(fn {_, beam} -> get_abstract_code(beam) end)
184  end
185
186  defp read_beams(escript_filename) do
187    # :zip.unzip/2 cannot unzip an escript unless we remove the escript header
188    zip_data = remove_escript_header(File.read!(escript_filename))
189    {:ok, tuples} = :zip.unzip(zip_data, [:memory])
190
191    for {filename, beam} <- tuples, Path.extname(filename) == ".beam" do
192      {filename, beam}
193    end
194  end
195
196  defp remove_escript_header(escript_data) do
197    {offset, _length} = :binary.match(escript_data, "\nPK")
198    zip_start = offset + 1
199    binary_part(escript_data, zip_start, byte_size(escript_data) - zip_start)
200  end
201
202  defp get_abstract_code(beam) do
203    case :beam_lib.chunks(beam, [:abstract_code]) do
204      {:ok, {_, [{:abstract_code, {_, abstract_code}}]}} -> abstract_code
205      _ -> nil
206    end
207  end
208
209  test "generate escript with path" do
210    in_fixture("escript_test", fn ->
211      Mix.Project.push(EscriptWithPath)
212
213      Mix.Tasks.Escript.Build.run([])
214
215      message = "Generated escript ebin/escript_test_with_path with MIX_ENV=dev"
216      assert_received {:mix_shell, :info, [^message]}
217
218      assert System.cmd("escript", ["ebin/escript_test_with_path"]) == {"TEST\n", 0}
219    end)
220  end
221
222  test "generate escript with deps" do
223    in_fixture("escript_test", fn ->
224      Mix.Project.push(EscriptWithDeps)
225
226      Mix.Tasks.Escript.Build.run([])
227
228      message = "Generated escript escript_test_with_deps with MIX_ENV=dev"
229      assert_received {:mix_shell, :info, [^message]}
230
231      assert System.cmd("escript", ["escript_test_with_deps"]) == {"TEST\n", 0}
232    end)
233  after
234    purge([Ok.MixProject])
235  end
236
237  test "generate escript with Erlang and deps" do
238    in_fixture("escript_test", fn ->
239      Mix.Project.push(EscriptErlangWithDeps)
240
241      Mix.Tasks.Escript.Build.run([])
242
243      message = "Generated escript escript_test_erlang_with_deps with MIX_ENV=dev"
244      assert_received {:mix_shell, :info, [^message]}
245
246      assert System.cmd("escript", ["escript_test_erlang_with_deps", "arg1", "arg2"]) ==
247               {~s(["arg1","arg2"]), 0}
248    end)
249  after
250    purge([Ok.MixProject])
251  end
252
253  test "generate escript with Erlang main module" do
254    in_fixture("escript_test", fn ->
255      Mix.Project.push(EscriptErlangMainModule)
256
257      Mix.Tasks.Escript.Build.run([])
258
259      message = "Generated escript escript_test_erlang_main_module with MIX_ENV=dev"
260      assert_received {:mix_shell, :info, [^message]}
261
262      assert System.cmd("escript", ["escript_test_erlang_main_module", "arg1", "arg2"]) ==
263               {~s([<<"arg1">>,<<"arg2">>]), 0}
264    end)
265  after
266    purge([Ok.MixProject])
267  end
268
269  test "generate escript with consolidated protocols" do
270    in_fixture("escript_test", fn ->
271      Mix.Project.push(EscriptConsolidated)
272
273      Mix.Tasks.Escript.Build.run([])
274
275      message = "Generated escript escript_test_consolidated with MIX_ENV=dev"
276      assert_received {:mix_shell, :info, [^message]}
277
278      assert System.cmd("escript", ["escript_test_consolidated", "--protocol", "Enumerable"]) ==
279               {"true\n", 0}
280    end)
281  end
282
283  test "escript install and uninstall" do
284    File.rm_rf!(tmp_path(".mix/escripts"))
285
286    in_fixture("escript_test", fn ->
287      Mix.Project.push(Escript)
288
289      # check that no escripts are installed
290      Mix.Tasks.Escript.run([])
291      assert_received {:mix_shell, :info, ["No escripts currently installed."]}
292
293      # build and install our escript
294      send(self(), {:mix_shell_input, :yes?, true})
295      Mix.Tasks.Escript.Install.run([])
296
297      # check that it shows in the list
298      Mix.Tasks.Escript.run([])
299      assert_received {:mix_shell, :info, ["* escript_test"]}
300      refute_received {:mix_shell, :info, ["* escript_test.bat"]}
301
302      # check uninstall confirmation
303      send(self(), {:mix_shell_input, :yes?, false})
304      Mix.Tasks.Escript.Uninstall.run(["escript_test"])
305      assert File.regular?(tmp_path(".mix/escripts/escript_test"))
306
307      # uninstall the escript
308      send(self(), {:mix_shell_input, :yes?, true})
309      Mix.Tasks.Escript.Uninstall.run(["escript_test"])
310      refute File.regular?(tmp_path(".mix/escripts/escript_test"))
311      refute File.regular?(tmp_path(".mix/escripts/escript_test.bat"))
312
313      # check that no escripts remain
314      Mix.Tasks.Escript.run([])
315      assert_received {:mix_shell, :info, ["No escripts currently installed."]}
316    end)
317  end
318
319  test "escript install and uninstall --force" do
320    File.rm_rf!(tmp_path(".mix/escripts"))
321
322    in_fixture("escript_test", fn ->
323      Mix.Project.push(Escript)
324
325      Mix.Tasks.Escript.Install.run(["--force"])
326
327      # check that it shows in the list
328      Mix.Tasks.Escript.run([])
329      assert_received {:mix_shell, :info, ["* escript_test"]}
330      refute_received {:mix_shell, :info, ["* escript_test.bat"]}
331
332      # uninstall the escript
333      Mix.Tasks.Escript.Uninstall.run(["escript_test", "--force"])
334
335      # check that no escripts remain
336      Mix.Tasks.Escript.run([])
337      assert_received {:mix_shell, :info, ["No escripts currently installed."]}
338    end)
339  end
340
341  test "escript invalid install" do
342    # Install our escript
343    send(self(), {:mix_shell_input, :yes?, true})
344    message = "The given path does not point to an escript, installation aborted"
345
346    assert_raise Mix.Error, message, fn ->
347      Mix.Tasks.Escript.Install.run([__ENV__.file])
348    end
349  end
350
351  test "escript invalid main module" do
352    in_fixture("escript_test", fn ->
353      Mix.Project.push(EscriptWithUnknownMainModule)
354
355      message =
356        "Could not generate escript, module Elixir.BogusEscriptTest defined as :main_module could not be loaded"
357
358      assert_raise Mix.Error, message, fn ->
359        Mix.Tasks.Escript.Build.run([])
360      end
361    end)
362  end
363
364  test "escript.install from Git" do
365    in_fixture("git_repo", fn ->
366      File.mkdir_p!("config")
367
368      File.write!("config/config.exs", """
369      import Config
370      config :git_repo, :escript_config, true
371      """)
372
373      File.write!("lib/git_repo.ex", """
374      require Application
375      true = Application.compile_env!(:git_repo, :escript_config)
376
377      defmodule GitRepo do
378        def main(_argv) do
379          IO.puts("TEST")
380        end
381      end
382      """)
383
384      File.write!("mix.exs", """
385      defmodule GitRepo.MixProject do
386        use Mix.Project
387
388        def project do
389          [app: :git_repo, version: "0.1.0", escript: [main_module: GitRepo]]
390        end
391      end
392      """)
393
394      System.cmd("git", ~w[add .])
395      System.cmd("git", ~w[commit -m "ok"])
396
397      send(self(), {:mix_shell_input, :yes?, true})
398      Mix.Tasks.Escript.Install.run(["git", File.cwd!()])
399      assert_received {:mix_shell, :info, ["Generated escript git_repo with MIX_ENV=prod"]}
400
401      escript_path = Path.join([tmp_path(".mix"), "escripts", "git_repo"])
402      assert System.cmd("escript", [escript_path]) == {"TEST\n", 0}
403    end)
404  after
405    purge([GitRepo, GitRepo.MixProject])
406  end
407end
408