1Code.require_file("../../test_helper.exs", __DIR__) 2 3defmodule Mix.Tasks.Compile.ProtocolsTest do 4 use MixTest.Case 5 6 @old {{2010, 1, 1}, {0, 0, 0}} 7 8 test "compiles and consolidates local protocols", context do 9 in_tmp(context.test, fn -> 10 Mix.Project.push(MixTest.Case.Sample) 11 12 File.mkdir_p!("lib") 13 assert Mix.Task.run("compile") 14 15 # Define a local protocol 16 File.write!("lib/protocol.ex", """ 17 defprotocol Compile.Protocol do 18 def foo(a, b) 19 end 20 """) 21 22 assert compile_elixir_and_protocols() == :ok 23 mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.Compile.Protocol.beam") 24 25 # Implement a local protocol 26 File.write!("lib/impl.ex", """ 27 defimpl Compile.Protocol, for: Integer do 28 def foo(a, b), do: a + b 29 end 30 """) 31 32 assert compile_elixir_and_protocols() == :ok 33 34 assert mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.Compile.Protocol.beam") != 35 @old 36 37 # Delete a local implementation 38 File.rm!("lib/impl.ex") 39 assert compile_elixir_and_protocols() == :ok 40 41 assert mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.Compile.Protocol.beam") != 42 @old 43 44 # Delete a local protocol 45 File.rm!("lib/protocol.ex") 46 assert compile_elixir_and_protocols() == :noop 47 refute File.regular?("_build/dev/lib/sample/consolidated/Elixir.Compile.Protocol.beam") 48 end) 49 end 50 51 test "compiles after converting a protocol into a standard module", context do 52 in_tmp(context.test, fn -> 53 Mix.Project.push(MixTest.Case.Sample) 54 55 File.mkdir_p!("lib") 56 Mix.Task.run("compile") 57 purge_protocol(Compile.Protocol) 58 59 # Define a local protocol 60 File.write!("lib/protocol.ex", """ 61 defprotocol Compile.Protocol do 62 def foo(a) 63 end 64 65 defimpl Compile.Protocol, for: Integer do 66 def foo(a), do: a 67 end 68 """) 69 70 assert compile_elixir_and_protocols() == :ok 71 mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.Compile.Protocol.beam") 72 File.rm!("lib/protocol.ex") 73 74 # Define a standard module 75 File.write!("lib/protocol.ex", """ 76 defmodule Compile.Protocol do 77 end 78 """) 79 80 assert compile_elixir_and_protocols() == :noop 81 82 # Delete a local protocol 83 File.rm!("lib/protocol.ex") 84 assert compile_elixir_and_protocols() == :noop 85 refute File.regular?("_build/dev/lib/sample/consolidated/Elixir.Compile.Protocol.beam") 86 end) 87 end 88 89 test "compiles and consolidates deps protocols", context do 90 in_tmp(context.test, fn -> 91 Mix.Project.push(MixTest.Case.Sample) 92 93 File.mkdir_p!("lib") 94 Mix.Task.run("compile") 95 purge_protocol(String.Chars) 96 mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.String.Chars.beam") 97 98 assert compile_elixir_and_protocols() == :noop 99 assert mtime("_build/dev/lib/sample/consolidated/Elixir.String.Chars.beam") == @old 100 101 # Implement a deps protocol 102 File.write!("lib/struct.ex", """ 103 defmodule Compile.Protocol.Struct do 104 defstruct a: nil 105 defimpl String.Chars do 106 def to_string(_), do: "ok" 107 end 108 end 109 """) 110 111 assert compile_elixir_and_protocols() == :ok 112 assert mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.String.Chars.beam") != @old 113 114 # Delete the local implementation 115 File.rm!("lib/struct.ex") 116 assert compile_elixir_and_protocols() == :ok 117 assert mark_as_old!("_build/dev/lib/sample/consolidated/Elixir.String.Chars.beam") != @old 118 end) 119 end 120 121 test "consolidated protocols keep relative path to their source" do 122 in_fixture("no_mixfile", fn -> 123 Mix.Project.push(MixTest.Case.Sample) 124 compile_elixir_and_protocols() 125 126 # Load consolidated 127 :code.add_patha('_build/dev/lib/sample/consolidated') 128 :code.purge(Enumerable) 129 :code.delete(Enumerable) 130 131 try do 132 Enumerable.impl_for!(:oops) 133 rescue 134 Protocol.UndefinedError -> 135 assert [{_, _, _, [file: 'lib/enum.ex'] ++ _} | _] = __STACKTRACE__ 136 else 137 _ -> 138 flunk("Enumerable.impl_for!/1 should have failed") 139 after 140 purge_protocol(Enumerable) 141 end 142 end) 143 end 144 145 defp compile_elixir_and_protocols do 146 Mix.Tasks.Compile.Elixir.run([]) 147 Mix.Tasks.Compile.Protocols.run([]) 148 end 149 150 defp mtime(path) do 151 File.stat!(path).mtime 152 end 153 154 defp mark_as_old!(path) do 155 mtime = mtime(path) 156 File.touch!(path, @old) 157 mtime 158 end 159 160 defp purge_protocol(module) do 161 :code.del_path(:filename.absname('_build/dev/lib/sample/consolidated')) 162 :code.purge(module) 163 :code.delete(module) 164 end 165end 166