migrator.exs (7446B)
1 Code.require_file "../support/file_helpers.exs", __DIR__ 2 3 defmodule Ecto.Integration.MigratorTest do 4 use Ecto.Integration.Case 5 6 import Support.FileHelpers 7 import ExUnit.CaptureLog 8 import Ecto.Migrator 9 10 alias Ecto.Integration.{TestRepo, PoolRepo} 11 alias Ecto.Migration.SchemaMigration 12 13 setup config do 14 Process.register(self(), config.test) 15 PoolRepo.delete_all(SchemaMigration) 16 :ok 17 end 18 19 defmodule AnotherSchemaMigration do 20 use Ecto.Migration 21 22 def change do 23 execute TestRepo.create_prefix("bad_schema_migrations"), 24 TestRepo.drop_prefix("bad_schema_migrations") 25 26 create table(:schema_migrations, prefix: "bad_schema_migrations") do 27 add :version, :string 28 add :inserted_at, :integer 29 end 30 end 31 end 32 33 defmodule BrokenLinkMigration do 34 use Ecto.Migration 35 36 def change do 37 Task.start_link(fn -> raise "oops" end) 38 Process.sleep(:infinity) 39 end 40 end 41 42 defmodule GoodMigration do 43 use Ecto.Migration 44 45 def up do 46 create table(:good_migration) 47 end 48 49 def down do 50 drop table(:good_migration) 51 end 52 end 53 54 defmodule BadMigration do 55 use Ecto.Migration 56 57 def change do 58 execute "CREATE WHAT" 59 end 60 end 61 62 test "migrations up and down" do 63 assert migrated_versions(PoolRepo) == [] 64 assert up(PoolRepo, 31, GoodMigration, log: false) == :ok 65 66 [migration] = PoolRepo.all(SchemaMigration) 67 assert migration.version == 31 68 assert migration.inserted_at 69 70 assert migrated_versions(PoolRepo) == [31] 71 assert up(PoolRepo, 31, GoodMigration, log: false) == :already_up 72 assert migrated_versions(PoolRepo) == [31] 73 assert down(PoolRepo, 32, GoodMigration, log: false) == :already_down 74 assert migrated_versions(PoolRepo) == [31] 75 assert down(PoolRepo, 31, GoodMigration, log: false) == :ok 76 assert migrated_versions(PoolRepo) == [] 77 end 78 79 @tag :prefix 80 test "does not commit migration if insert into schema migration fails" do 81 # First we create a new schema migration table in another prefix 82 assert up(PoolRepo, 33, AnotherSchemaMigration, log: false) == :ok 83 assert migrated_versions(PoolRepo) == [33] 84 85 catch_error(up(PoolRepo, 34, GoodMigration, log: false, prefix: "bad_schema_migrations")) 86 catch_error(PoolRepo.all("good_migration")) 87 catch_error(PoolRepo.all("good_migration", prefix: "bad_schema_migrations")) 88 89 assert down(PoolRepo, 33, AnotherSchemaMigration, log: false) == :ok 90 end 91 92 test "ecto-generated migration queries pass schema_migration in telemetry options" do 93 handler = fn _event_name, _measurements, metadata -> 94 send(self(), metadata) 95 end 96 97 # migration table creation 98 Process.put(:telemetry, handler) 99 migrated_versions(PoolRepo, log: false) 100 assert_received %{options: [schema_migration: true]} 101 102 # transaction begin statement 103 Process.put(:telemetry, handler) 104 migrated_versions(PoolRepo, skip_table_creation: true, log: false) 105 assert_received %{options: [schema_migration: true]} 106 107 # retrieving the migration versions 108 Process.put(:telemetry, handler) 109 migrated_versions(PoolRepo, migration_lock: false, skip_table_creation: true, log: false) 110 assert_received %{options: [schema_migration: true]} 111 end 112 113 test "bad execute migration" do 114 assert catch_error(up(PoolRepo, 31, BadMigration, log: false)) 115 end 116 117 test "broken link migration" do 118 Process.flag(:trap_exit, true) 119 120 assert capture_log(fn -> 121 {:ok, pid} = Task.start_link(fn -> up(PoolRepo, 31, BrokenLinkMigration, log: false) end) 122 assert_receive {:EXIT, ^pid, _} 123 end) =~ "oops" 124 125 assert capture_log(fn -> 126 catch_exit(up(PoolRepo, 31, BrokenLinkMigration, log: false)) 127 end) =~ "oops" 128 end 129 130 test "run up to/step migration", config do 131 in_tmp fn path -> 132 create_migration(47, config) 133 create_migration(48, config) 134 135 assert [47] = run(PoolRepo, path, :up, step: 1, log: false) 136 assert count_entries() == 1 137 138 assert [48] = run(PoolRepo, path, :up, to: 48, log: false) 139 end 140 end 141 142 test "run down to/step migration", config do 143 in_tmp fn path -> 144 migrations = [ 145 create_migration(49, config), 146 create_migration(50, config), 147 ] 148 149 assert [49, 50] = run(PoolRepo, path, :up, all: true, log: false) 150 purge migrations 151 152 assert [50] = run(PoolRepo, path, :down, step: 1, log: false) 153 purge migrations 154 155 assert count_entries() == 1 156 assert [50] = run(PoolRepo, path, :up, to: 50, log: false) 157 end 158 end 159 160 test "runs all migrations", config do 161 in_tmp fn path -> 162 migrations = [ 163 create_migration(53, config), 164 create_migration(54, config), 165 ] 166 167 assert [53, 54] = run(PoolRepo, path, :up, all: true, log: false) 168 assert [] = run(PoolRepo, path, :up, all: true, log: false) 169 purge migrations 170 171 assert [54, 53] = run(PoolRepo, path, :down, all: true, log: false) 172 purge migrations 173 174 assert count_entries() == 0 175 assert [53, 54] = run(PoolRepo, path, :up, all: true, log: false) 176 end 177 end 178 179 test "does not commit half transactions on bad syntax", config do 180 in_tmp fn path -> 181 migrations = [ 182 create_migration(64, config), 183 create_migration("65_+", config) 184 ] 185 186 assert_raise SyntaxError, fn -> 187 run(PoolRepo, path, :up, all: true, log: false) 188 end 189 190 refute_received {:up, _} 191 assert count_entries() == 0 192 purge migrations 193 end 194 end 195 196 @tag :lock_for_migrations 197 test "raises when connection pool is too small" do 198 config = Application.fetch_env!(:ecto_sql, PoolRepo) 199 config = Keyword.merge(config, pool_size: 1) 200 Application.put_env(:ecto_sql, __MODULE__.SingleConnectionRepo, config) 201 202 defmodule SingleConnectionRepo do 203 use Ecto.Repo, otp_app: :ecto_sql, adapter: PoolRepo.__adapter__() 204 end 205 206 {:ok, _pid} = SingleConnectionRepo.start_link() 207 208 in_tmp fn path -> 209 exception_message = ~r/Migrations failed to run because the connection pool size is less than 2/ 210 211 assert_raise Ecto.MigrationError, exception_message, fn -> 212 run(SingleConnectionRepo, path, :up, all: true, log: false) 213 end 214 end 215 end 216 217 test "does not raise when connection pool is too small but there is no lock" do 218 config = Application.fetch_env!(:ecto_sql, PoolRepo) 219 config = Keyword.merge(config, pool_size: 1, migration_lock: nil) 220 Application.put_env(:ecto_sql, __MODULE__.SingleConnectionNoLockRepo, config) 221 222 defmodule SingleConnectionNoLockRepo do 223 use Ecto.Repo, otp_app: :ecto_sql, adapter: PoolRepo.__adapter__() 224 end 225 226 {:ok, _pid} = SingleConnectionNoLockRepo.start_link() 227 228 in_tmp fn path -> 229 run(SingleConnectionNoLockRepo, path, :up, all: true, log: false) 230 end 231 end 232 233 defp count_entries() do 234 PoolRepo.aggregate(SchemaMigration, :count, :version) 235 end 236 237 defp create_migration(num, config) do 238 module = Module.concat(__MODULE__, "Migration#{num}") 239 240 File.write! "#{num}_migration_#{num}.exs", """ 241 defmodule #{module} do 242 use Ecto.Migration 243 244 def up do 245 send #{inspect config.test}, {:up, #{inspect num}} 246 end 247 248 def down do 249 send #{inspect config.test}, {:down, #{inspect num}} 250 end 251 end 252 """ 253 254 module 255 end 256 257 defp purge(modules) do 258 Enum.each(List.wrap(modules), fn m -> 259 :code.delete m 260 :code.purge m 261 end) 262 end 263 end