Current section

Files

Jump to
ecto integration_test sql transaction.exs
Raw

integration_test/sql/transaction.exs

defmodule Ecto.Integration.TransactionTest do
# We can keep this test async as long as it
# is the only one access the transactions table
use ExUnit.Case, async: true
import Ecto.Query
alias Ecto.Integration.PoolRepo
alias Ecto.Integration.TestRepo
defmodule UniqueError do
defexception [:message]
end
setup do
PoolRepo.delete_all "transactions"
:ok
end
defmodule Trans do
use Ecto.Model
schema "transactions" do
field :text, :string
end
end
test "transaction returns value" do
x = PoolRepo.transaction(fn ->
PoolRepo.transaction(fn ->
42
end)
end)
assert x == {:ok, {:ok, 42}}
end
test "transaction re-raises" do
assert_raise UniqueError, fn ->
PoolRepo.transaction(fn ->
PoolRepo.transaction(fn ->
raise UniqueError
end)
end)
end
end
test "transaction commits" do
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "1"})
assert [^e] = PoolRepo.all(Trans)
assert [] = TestRepo.all(Trans)
end)
assert [%Trans{text: "1"}] = TestRepo.all(Trans)
end
test "transaction rolls back" do
try do
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "2"})
assert [^e] = PoolRepo.all(Trans)
assert [] = TestRepo.all(Trans)
raise UniqueError
end)
rescue
UniqueError -> :ok
end
assert [] = TestRepo.all(Trans)
end
test "transaction rolls back per repository" do
message = "cannot call rollback outside of transaction"
assert_raise RuntimeError, message, fn ->
PoolRepo.rollback(:done)
end
assert_raise RuntimeError, message, fn ->
TestRepo.transaction fn ->
PoolRepo.rollback(:done)
end
end
end
test "nested transaction partial rollback" do
assert PoolRepo.transaction(fn ->
e1 = PoolRepo.insert!(%Trans{text: "3"})
assert [^e1] = PoolRepo.all(Trans)
try do
PoolRepo.transaction(fn ->
e2 = PoolRepo.insert!(%Trans{text: "4"})
assert [^e1, ^e2] = PoolRepo.all(from(t in Trans, order_by: t.text))
raise UniqueError
end)
rescue
UniqueError -> :ok
end
assert {:noconnect, _} = catch_exit(PoolRepo.insert!(%Trans{text: "5"}))
end) == {:error, :rollback}
assert TestRepo.all(Trans) == []
end
test "manual rollback doesn't bubble up" do
x = PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "6"})
assert [^e] = PoolRepo.all(Trans)
PoolRepo.rollback(:oops)
end)
assert x == {:error, :oops}
assert [] = TestRepo.all(Trans)
end
test "manual rollback bubbles up on nested transaction" do
x = PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "6"})
assert [^e] = PoolRepo.all(Trans)
assert {:error, :oops} = PoolRepo.transaction(fn ->
PoolRepo.rollback(:oops)
end)
assert {:noconnect, _} = catch_exit(PoolRepo.insert!(%Trans{text: "5"}))
end)
assert x == {:error, :rollback}
assert [] = TestRepo.all(Trans)
end
test "transactions are not shared in repo" do
pid = self
new_pid = spawn_link fn ->
PoolRepo.transaction(fn ->
e = PoolRepo.insert!(%Trans{text: "7"})
assert [^e] = PoolRepo.all(Trans)
send(pid, :in_transaction)
receive do
:commit -> :ok
after
5000 -> raise "timeout"
end
end)
send(pid, :committed)
end
receive do
:in_transaction -> :ok
after
5000 -> raise "timeout"
end
assert [] = PoolRepo.all(Trans)
send(new_pid, :commit)
receive do
:committed -> :ok
after
5000 -> raise "timeout"
end
assert [%Trans{text: "7"}] = PoolRepo.all(Trans)
end
## Failures when logging
@tag :strict_savepoint
test "log raises after begin, drops transaction" do
try do
Process.put(:on_log, fn -> raise UniqueError end)
PoolRepo.transaction(fn -> end)
rescue
UniqueError -> :ok
end
# If it doesn't fail, the transaction was not closed properly.
catch_error(Ecto.Adapters.SQL.query!(PoolRepo, "savepoint foobar", []))
end
test "log raises after begin, drops the whole transaction" do
try do
PoolRepo.transaction(fn ->
PoolRepo.insert!(%Trans{text: "8"})
Process.put(:on_log, fn -> raise UniqueError end)
PoolRepo.transaction(fn -> flunk "log did not raise" end)
end)
rescue
UniqueError -> :ok
end
assert [] = PoolRepo.all(Trans)
end
test "log raises after commit, does commit" do
try do
PoolRepo.transaction(fn ->
PoolRepo.insert!(%Trans{text: "10"})
Process.put(:on_log, fn -> raise UniqueError end)
end)
rescue
UniqueError -> :ok
end
assert [%Trans{text: "10"}] = PoolRepo.all(Trans)
end
test "log raises after rollback, does rollback" do
try do
PoolRepo.transaction(fn ->
PoolRepo.insert!(%Trans{text: "11"})
Process.put(:on_log, fn -> raise UniqueError end)
PoolRepo.rollback(:rollback)
end)
rescue
UniqueError -> :ok
end
assert [] = PoolRepo.all(Trans)
end
## Timeouts
# Those tests are very sensitive to timeouts and
# may fail in CI servers and similar. They are
# tagged so they can be easily skipped.
# TODO: Uncomment those tests with Elixir 1.1
# and by default exclude the tests in build
# @tag :timeout_sensitive
# test "transaction exit includes :timeout on begin timeout" do
# assert match?({:timeout, _},
# catch_exit(PoolRepo.transaction([timeout: 0], fn ->
# flunk "did not timeout"
# end)))
# end
# @tag :timeout_sensitive
# test "transaction exit includes :timeout on query timeout" do
# assert match?({:timeout, _},
# catch_exit(PoolRepo.transaction(fn ->
# PoolRepo.transaction(fn ->
# PoolRepo.insert!(%Trans{text: "13"}, [timeout: 0])
# end)
# end)))
# assert [] = PoolRepo.all(Trans)
# end
end