1Code.require_file("../../test_helper.exs", __DIR__)
2
3defmodule Mix.Tasks.ArchiveTest do
4  use MixTest.Case
5
6  defmodule ArchiveProject do
7    def project do
8      [app: :archive, version: "0.1.0", elixir: "~> 0.1.0"]
9    end
10  end
11
12  defmodule ArchiveProject2 do
13    def project do
14      [app: :archive, version: "0.2.0"]
15    end
16  end
17
18  setup do
19    File.rm_rf!(tmp_path("userhome"))
20    System.put_env("MIX_ARCHIVES", tmp_path("userhome/.mix/archives/"))
21    Mix.Project.push(ArchiveProject)
22
23    on_exit(fn ->
24      Mix.Local.remove_archives()
25      System.delete_env("MIX_ARCHIVES")
26    end)
27  end
28
29  test "archive build" do
30    in_fixture("archive", fn ->
31      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
32      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
33      assert_received {:mix_shell, :info, [^message]}
34      assert File.regular?('archive-0.1.0.ez')
35
36      assert_archive_content_default()
37      refute has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/.dot_file')
38    end)
39  end
40
41  test "archive build with include-dot-files" do
42    in_fixture("archive", fn ->
43      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check", "--include-dot-files"])
44      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
45      assert_received {:mix_shell, :info, [^message]}
46      assert File.regular?('archive-0.1.0.ez')
47
48      assert_archive_content_default()
49      assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/.dot_file')
50    end)
51  end
52
53  def assert_archive_content_default() do
54    assert File.regular?('archive-0.1.0.ez')
55    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/.elixir')
56    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/priv/not_really_an.so')
57
58    assert has_in_zip_file?(
59             'archive-0.1.0.ez',
60             'archive-0.1.0/ebin/Elixir.Mix.Tasks.Local.Sample.beam'
61           )
62
63    assert has_in_zip_file?('archive-0.1.0.ez', 'archive-0.1.0/ebin/archive.app')
64  end
65
66  test "archive install" do
67    in_fixture("archive", fn ->
68      # Build and install archive
69      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
70
71      send(self(), {:mix_shell_input, :yes?, true})
72      Mix.Tasks.Archive.Install.run([])
73      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
74      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
75
76      # Check that the version warning is printed after installation
77      version_error =
78        "warning: the archive archive-0.1.0 requires Elixir \"~> 0.1.0\" " <>
79          "but you are running on v#{System.version()}"
80
81      assert_received {:mix_shell, :error, [^version_error]}
82
83      archive = tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin")
84      assert to_charlist(archive) in :code.get_path()
85
86      # Loading the archive should emit warning again
87      Mix.Local.append_archives()
88      assert_received {:mix_shell, :error, [^version_error]}
89
90      # List archive
91      Mix.Tasks.Local.run([])
92      info = "mix local.sample # A local install sample"
93      assert_received {:mix_shell, :info, [^info]}
94
95      Mix.Tasks.Archive.run([])
96      assert_received {:mix_shell, :info, ["* archive-0.1.0"]}
97
98      # Run archived task
99      Mix.Task.run("local.sample")
100      assert_received {:mix_shell, :info, ["sample"]}
101    end)
102  end
103
104  test "archive install invalid file" do
105    in_fixture("archive", fn ->
106      file_name = "invalid-archive-0.1.0.ez"
107      assert File.regular?(file_name)
108
109      send(self(), {:mix_shell_input, :yes?, true})
110
111      assert_raise Mix.Error, ~r/invalid archive file/, fn ->
112        Mix.Tasks.Archive.Install.run([file_name])
113      end
114    end)
115  end
116
117  test "archive install missing file" do
118    message = ~r[Expected "./unlikely-to-exist-0.1.0.ez" to be a local file path]
119
120    assert_raise Mix.Error, message, fn ->
121      Mix.Tasks.Archive.Install.run(["./unlikely-to-exist-0.1.0.ez"])
122    end
123  end
124
125  test "archive install --force" do
126    in_fixture("archive", fn ->
127      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
128      Mix.Tasks.Archive.Install.run(["--force"])
129
130      message = "Generated archive \"archive-0.1.0.ez\" with MIX_ENV=dev"
131      assert_received {:mix_shell, :info, [^message]}
132
133      Mix.Tasks.Archive.Uninstall.run(["archive-0.1.0", "--force"])
134      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
135    end)
136  end
137
138  test "archive.install from Git" do
139    in_fixture("git_repo", fn ->
140      send(self(), {:mix_shell_input, :yes?, true})
141      Mix.Tasks.Archive.Install.run(["git", File.cwd!()])
142
143      message = "Generated archive \"git_repo-0.1.0.ez\" with MIX_ENV=prod"
144      assert_received {:mix_shell, :info, [^message]}
145
146      refute File.regular?(tmp_path("userhome/.mix/archives/git_repo-0.1.0.ez"))
147      assert File.dir?(tmp_path("userhome/.mix/archives/git_repo-0.1.0/git_repo-0.1.0/ebin"))
148    end)
149  after
150    purge([GitRepo, GitRepo.MixProject])
151  end
152
153  test "archive install, update, and uninstall life-cycle" do
154    in_fixture("archive", fn ->
155      # Install previous version
156      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
157      assert File.regular?("archive-0.1.0.ez")
158      send(self(), {:mix_shell_input, :yes?, true})
159      Mix.Tasks.Archive.Install.run([])
160      assert_received {:mix_shell, :error, [_]}
161
162      # Build new version
163      Mix.Project.push(ArchiveProject2)
164      Mix.Tasks.Archive.Build.run(["--no-compile"])
165      assert File.regular?("archive-0.2.0.ez")
166
167      message = "Generated archive \"archive-0.2.0.ez\" with MIX_ENV=dev"
168      assert_received {:mix_shell, :info, [^message]}
169
170      # Install new version
171      send(self(), {:mix_shell_input, :yes?, true})
172      Mix.Tasks.Archive.Install.run([])
173      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.2.0.ez"))
174      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
175
176      # Re-install current version should not change system
177      send(self(), {:mix_shell_input, :yes?, true})
178      Mix.Tasks.Archive.Install.run([])
179      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.2.0.ez"))
180      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
181
182      # Try to install a missing version does not remove archive
183      assert_raise Mix.Error, fn ->
184        Mix.Tasks.Archive.Install.run(["./archive-0.0.0.ez"])
185      end
186
187      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
188      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
189
190      # Load archive without warnings because there is no :elixir requirement in mix.exs
191      Mix.Local.append_archives()
192      refute_received {:mix_shell, :error, [_]}
193
194      # Check uninstall confirmation
195      send(self(), {:mix_shell_input, :yes?, false})
196      Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
197      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
198
199      # Remove it!
200      send(self(), {:mix_shell_input, :yes?, true})
201      Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
202      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
203
204      # Check old paths are unloaded
205      paths = Enum.map(:code.get_path(), &List.to_string/1)
206      refute tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin") in paths
207    end)
208  end
209
210  test "archive uninstall without version" do
211    in_fixture("archive", fn ->
212      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
213      send(self(), {:mix_shell_input, :yes?, true})
214      Mix.Tasks.Archive.Install.run([])
215
216      send(self(), {:mix_shell_input, :yes?, false})
217      Mix.Tasks.Archive.Uninstall.run(["archive"])
218      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
219
220      send(self(), {:mix_shell_input, :yes?, true})
221      Mix.Tasks.Archive.Uninstall.run(["archive"])
222      refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
223    end)
224  end
225
226  test "archive checksum" do
227    in_fixture("archive", fn ->
228      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
229      assert File.regular?("archive-0.1.0.ez")
230      send(self(), {:mix_shell_input, :yes?, true})
231
232      # Install with wrong checksum
233      assert_raise Mix.Error, ~r"Data does not match the given SHA-512 checksum", fn ->
234        send(self(), {:mix_shell_input, :yes?, true})
235        Mix.Tasks.Archive.Install.run(["--sha512", "wrong"])
236      end
237
238      # Install with correct checksum
239      send(self(), {:mix_shell_input, :yes?, true})
240      Mix.Tasks.Archive.Install.run(["--sha512", sha512("archive-0.1.0.ez")])
241      refute File.regular?(tmp_path("userhome/.mix/archives/archive-0.1.0.ez"))
242      assert File.dir?(tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin"))
243    end)
244  end
245
246  test "archive check" do
247    # Install the archive
248    in_fixture("archive", fn ->
249      Mix.Tasks.Archive.Build.run(["--no-elixir-version-check"])
250      send(self(), {:mix_shell_input, :yes?, true})
251      Mix.Tasks.Archive.Install.run([])
252      Application.unload(:archive)
253    end)
254
255    assert_raise Mix.Error, ~r/Expected archive to be in the format/, fn ->
256      archive_check([:archive])
257    end
258
259    assert_raise Mix.Error, ~r/Archive "archive" could not be found/, fn ->
260      archive_check([{:archive, ">= 1.0.0"}])
261    end
262
263    # Load the archive
264    Mix.Local.append_archives()
265
266    message = ~r/Archive \"archive-0.1.0\" does not match requirement >= 1.0.0/
267
268    assert_raise Mix.Error, message, fn ->
269      archive_check([{:archive, ">= 1.0.0"}])
270    end
271
272    archive_check([{:archive, ">= 0.0.0"}])
273  end
274
275  defp archive_check(archives) do
276    Mix.Project.pop()
277    Mix.ProjectStack.post_config(archives: archives)
278    Mix.Project.push(MixTest.Case.Sample)
279    Mix.Tasks.Archive.Check.run([])
280  end
281
282  defp sha512(file) do
283    Base.encode16(:crypto.hash(:sha512, File.read!(file)), case: :lower)
284  end
285
286  defp has_in_zip_file?(archive, name) do
287    {:ok, files} = :zip.list_dir(archive)
288    Enum.find(files, &match?({:zip_file, ^name, _, _, _, _}, &1))
289  end
290end
291