diff --git a/test/vinyl/errinj.result b/test/vinyl/errinj.result
index 23ab845b3f087988bcfe8811d52e529e25580558..a16475f5eb512faa516a8eb7335a9ccf6d56e031 100644
--- a/test/vinyl/errinj.result
+++ b/test/vinyl/errinj.result
@@ -1,6 +1,3 @@
---
--- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE
---
 test_run = require('test_run').new()
 ---
 ...
@@ -17,42 +14,6 @@ errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.040)
 ---
 - ok
 ...
-s = box.schema.space.create('test', {engine='vinyl'})
----
-...
-_ = s:create_index('pk')
----
-...
-function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end
----
-...
-errinj.set("ERRINJ_WAL_WRITE", true)
----
-- ok
-...
-f()
----
-- error: Failed to write to disk
-...
-s:select{}
----
-- []
-...
-errinj.set("ERRINJ_WAL_WRITE", false)
----
-- ok
-...
-f()
----
-...
-s:select{}
----
-- - [1, 'hi']
-  - [2, 'bye']
-...
-s:drop()
----
-...
 --
 -- Lost data in case of dump error
 --
@@ -474,1765 +435,646 @@ errinj.set("ERRINJ_VY_SQUASH_TIMEOUT", 0)
 ---
 - ok
 ...
---https://github.com/tarantool/tarantool/issues/1842
---test error injection
-s = box.schema.space.create('test', {engine='vinyl'})
----
-...
-_ = s:create_index('pk')
+--
+-- Space drop in the middle of dump.
+--
+test_run:cmd("create server test with script='vinyl/low_quota.lua'")
 ---
+- true
 ...
-s:replace{0, 0}
+test_run:cmd("start server test with args='1048576'")
 ---
-- [0, 0]
+- true
 ...
-s:replace{1, 0}
+test_run:cmd('switch test')
 ---
-- [1, 0]
+- true
 ...
-s:replace{2, 0}
+fiber = require 'fiber'
 ---
-- [2, 0]
 ...
-errinj.set("ERRINJ_WAL_WRITE", true)
+box.cfg{vinyl_timeout = 0.001}
 ---
-- ok
 ...
-s:replace{3, 0}
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
-- error: Failed to write to disk
 ...
-s:replace{4, 0}
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
 ---
-- error: Failed to write to disk
 ...
-s:replace{5, 0}
+_ = s:create_index('i2', {parts = {2, 'unsigned'}})
 ---
-- error: Failed to write to disk
 ...
-s:replace{6, 0}
+_ = s:insert{1, 1}
 ---
-- error: Failed to write to disk
 ...
-errinj.set("ERRINJ_WAL_WRITE", false)
+-- Delay dump so that we can manage to drop the space
+-- while it is still being dumped.
+box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', true)
 ---
 - ok
 ...
-s:replace{7, 0}
----
-- [7, 0]
-...
-s:replace{8, 0}
+-- Before failing on quota timeout, the following fiber
+-- will trigger dump due to memory shortage.
+_ = fiber.create(function() s:insert{2, 2, string.rep('x', box.cfg.vinyl_memory)} end)
 ---
-- [8, 0]
 ...
-s:select{}
+-- Let the fiber run.
+fiber.sleep(0)
 ---
-- - [0, 0]
-  - [1, 0]
-  - [2, 0]
-  - [7, 0]
-  - [8, 0]
 ...
+-- Drop the space while the dump task is still running.
 s:drop()
 ---
 ...
-create_iterator = require('utils').create_iterator
----
-...
---iterator test
-test_run:cmd("setopt delimiter ';'")
+-- Wait for the dump task to complete.
+box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', false)
 ---
-- true
+- ok
 ...
-fiber_status = 0
-
-function fiber_func()
-    box.begin()
-    s:replace{5, 5}
-    fiber_status = 1
-    local res = {pcall(box.commit) }
-    fiber_status = 2
-    return unpack(res)
-end;
+box.snapshot()
 ---
+- ok
 ...
-test_run:cmd("setopt delimiter ''");
+--
+-- Check that all dump/compact tasks that are in progress at
+-- the time when the server stops are aborted immediately.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
-- true
 ...
-s = box.schema.space.create('test', {engine='vinyl'})
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
 ---
 ...
-_ = s:create_index('pk')
+_ = s:create_index('i2', {parts = {2, 'unsigned'}})
 ---
 ...
-fiber = require('fiber')
+box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01)
 ---
+- ok
 ...
-_ = s:replace{0, 0}
+for i = 1, 1000 do s:replace{i, i} end
 ---
 ...
-_ = s:replace{10, 0}
+_ = fiber.create(function() box.snapshot() end)
 ---
 ...
-_ = s:replace{20, 0}
+fiber.sleep(0.01)
 ---
 ...
-test_run:cmd("setopt delimiter ';'");
+test_run:cmd('switch default')
 ---
 - true
 ...
-faced_trash = false
-for i = 1,100 do
-    errinj.set("ERRINJ_WAL_WRITE", true)
-    local f = fiber.create(fiber_func)
-    local itr = create_iterator(s, {0}, {iterator='GE'})
-    local first = itr.next()
-    local second = itr.next()
-    if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end
-    while fiber_status <= 1 do fiber.sleep(0.001) end
-    local _,next = pcall(itr.next)
-    _,next = pcall(itr.next)
-    _,next = pcall(itr.next)
-    errinj.set("ERRINJ_WAL_WRITE", false)
-    s:delete{5}
-end;
+t1 = fiber.time()
 ---
 ...
-test_run:cmd("setopt delimiter ''");
+test_run:cmd("stop server test")
 ---
 - true
 ...
-faced_trash
----
-- false
-...
-s:drop()
+t2 = fiber.time()
 ---
 ...
--- TX in prepared but not committed state
-s = box.schema.space.create('test', {engine='vinyl'})
+t2 - t1 < 1
 ---
+- true
 ...
-_ = s:create_index('pk')
+test_run:cmd("cleanup server test")
 ---
+- true
 ...
-fiber = require('fiber')
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
 ...
-txn_proxy = require('txn_proxy')
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
 ---
 ...
-s:replace{1, "original"}
+c = 10
 ---
-- [1, 'original']
 ...
-s:replace{2, "original"}
+errinj.set("ERRINJ_WAL_WRITE_DISK", true)
 ---
-- [2, 'original']
+- ok
 ...
-s:replace{3, "original"}
+for i = 1,10 do fiber.create(function() pcall(s.replace, s, {i}) c = c - 1 end) end
 ---
-- [3, 'original']
 ...
-c0 = txn_proxy.new()
+while c ~= 0 do fiber.sleep(0.001) end
 ---
 ...
-c0:begin()
+s:select{}
 ---
-- 
+- []
 ...
-c1 = txn_proxy.new()
+errinj.set("ERRINJ_WAL_WRITE_DISK", false)
 ---
+- ok
 ...
-c1:begin()
+s:drop()
 ---
-- 
 ...
-c2 = txn_proxy.new()
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
 ...
-c2:begin()
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
 ---
-- 
 ...
-c3 = txn_proxy.new()
+for i = 0, 9 do s:replace({i, i + 1}) end
 ---
 ...
-c3:begin()
+box.snapshot()
 ---
-- 
+- ok
 ...
---
--- Prepared transactions
---
--- Pause WAL writer to cause all further calls to box.commit() to move
--- transactions into prepared, but not committed yet state.
-errinj.set("ERRINJ_WAL_DELAY", true)
+errinj.set("ERRINJ_XLOG_GARBAGE", true)
 ---
 - ok
 ...
-lsn = box.info.lsn
+s:select()
 ---
+- error: tx checksum mismatch
 ...
-c0('s:replace{1, "c0"}')
+errinj.set("ERRINJ_XLOG_GARBAGE", false)
 ---
-- - [1, 'c0']
+- ok
 ...
-c0('s:replace{2, "c0"}')
+errinj.set("ERRINJ_VYRUN_DATA_READ", true)
 ---
-- - [2, 'c0']
+- ok
 ...
-c0('s:replace{3, "c0"}')
+s:select()
 ---
-- - [3, 'c0']
+- error: failed to read from file
 ...
-_ = fiber.create(c0.commit, c0)
+errinj.set("ERRINJ_VYRUN_DATA_READ", false)
 ---
+- ok
 ...
-box.info.lsn == lsn
+s:select()
 ---
-- true
+- - [0, 1]
+  - [1, 2]
+  - [2, 3]
+  - [3, 4]
+  - [4, 5]
+  - [5, 6]
+  - [6, 7]
+  - [7, 8]
+  - [8, 9]
+  - [9, 10]
 ...
-c1('s:replace{1, "c1"}')
+s:drop()
 ---
-- - [1, 'c1']
 ...
-c1('s:replace{2, "c1"}')
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
-- - [2, 'c1']
 ...
-_ = fiber.create(c1.commit, c1)
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
 ---
 ...
-box.info.lsn == lsn
+for i = 0, 9 do s:replace({i, i + 1}) end
 ---
-- true
 ...
-c3('s:select{1}') -- c1 is visible
+errinj.set("ERRINJ_XLOG_GARBAGE", true)
 ---
-- - [[1, 'c1']]
+- ok
 ...
-c2('s:replace{1, "c2"}')
+box.snapshot()
 ---
-- - [1, 'c2']
+- error: tx checksum mismatch
 ...
-c2('s:replace{3, "c2"}')
+for i = 10, 19 do s:replace({i, i + 1}) end
 ---
-- - [3, 'c2']
 ...
-_ = fiber.create(c2.commit, c2)
+errinj.set("ERRINJ_XLOG_GARBAGE", false)
 ---
+- ok
 ...
-box.info.lsn == lsn
+box.snapshot()
 ---
-- true
+- ok
 ...
-c3('s:select{1}') -- c1 is visible, c2 is not
+s:select()
 ---
-- - [[1, 'c1']]
+- - [0, 1]
+  - [1, 2]
+  - [2, 3]
+  - [3, 4]
+  - [4, 5]
+  - [5, 6]
+  - [6, 7]
+  - [7, 8]
+  - [8, 9]
+  - [9, 10]
+  - [10, 11]
+  - [11, 12]
+  - [12, 13]
+  - [13, 14]
+  - [14, 15]
+  - [15, 16]
+  - [16, 17]
+  - [17, 18]
+  - [18, 19]
+  - [19, 20]
 ...
-c3('s:select{2}') -- c1 is visible
+s:drop()
 ---
-- - [[2, 'c1']]
 ...
-c3('s:select{3}') -- c2 is not visible
+-- Point select from secondary index during snapshot.
+-- Once upon time that leaded to crash.
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
-- - [[3, 'c0']]
 ...
--- Resume WAL writer and wait until all transactions will been committed
-errinj.set("ERRINJ_WAL_DELAY", false)
+i1 = s:create_index('pk', {parts = {1, 'uint'}, bloom_fpr = 0.5})
 ---
-- ok
 ...
-REQ_COUNT = 7
+i2 = s:create_index('sk', {parts = {2, 'uint'}, bloom_fpr = 0.5})
 ---
 ...
-while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end
+for i = 1,10 do s:replace{i, i, 0} end
 ---
 ...
-box.info.lsn == lsn + REQ_COUNT
+test_run:cmd("setopt delimiter ';'")
 ---
 - true
 ...
-c3('s:select{1}') -- c1 is visible, c2 is not
+function worker()
+    for i = 11,20,2 do
+        s:upsert({i, i}, {{'=', 3, 1}})
+        errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true)
+        i1:select{i}
+        s:upsert({i + 1 ,i + 1}, {{'=', 3, 1}})
+        errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true)
+        i2:select{i + 1}
+    end
+end
+test_run:cmd("setopt delimiter ''");
 ---
-- - [[1, 'c1']]
 ...
-c3('s:select{2}') -- c1 is visible
+f = fiber.create(worker)
 ---
-- - [[2, 'c1']]
 ...
-c3('s:select{3}') -- c2 is not visible
+while f:status() ~= 'dead' do box.snapshot() fiber.sleep(0.01) end
 ---
-- - [[3, 'c0']]
 ...
-c3:commit()
+errinj.set("ERRINJ_VY_POINT_ITER_WAIT", false)
 ---
-- 
+- ok
 ...
 s:drop()
 ---
 ...
---
--- Test mem restoration on a prepared and not commited statement
--- after moving iterator into read view.
---
-space = box.schema.space.create('test', {engine = 'vinyl'})
+-- vinyl: vy_cache_add: Assertion `0' failed
+-- https://github.com/tarantool/tarantool/issues/2685
+s = box.schema.create_space('test', {engine = 'vinyl'})
 ---
 ...
-pk = space:create_index('pk')
+pk = s:create_index('pk')
 ---
 ...
-space:replace{1}
+s:replace{2, 0}
 ---
-- [1]
+- [2, 0]
 ...
-space:replace{2}
+box.snapshot()
 ---
-- [2]
+- ok
 ...
-space:replace{3}
+s:replace{1, 0}
 ---
-- [3]
+- [1, 0]
 ...
-last_read = nil
+box.snapshot()
 ---
+- ok
 ...
-errinj.set("ERRINJ_WAL_DELAY", true)
+s:replace{0, 0}
 ---
-- ok
+- [0, 0]
 ...
-test_run:cmd("setopt delimiter ';'")
+s:select{0}
 ---
-- true
+- - [0, 0]
 ...
--- block until wal_delay = false
--- send iterator to read view
--- flush mem and update index version to trigger iterator restore
-function fill_space()
-    box.begin()
-    space:replace{1}
-    space:replace{2}
-    space:replace{3}
-    box.commit()
-    space:replace{1, 1}
-    box.snapshot()
-end;
+errinj.set("ERRINJ_WAL_DELAY", true)
 ---
+- ok
 ...
-function iterate_in_read_view()
-    local i = create_iterator(space)
-    last_read = i.next()
-    fiber.sleep(100000)
-    last_read = i.next()
-end;
+wait_replace = true
 ---
 ...
-test_run:cmd("setopt delimiter ''");
+_ = fiber.create(function() s:replace{1, 1} wait_replace = false end)
 ---
-- true
 ...
-f1 = fiber.create(fill_space)
+gen,param,state = s:pairs({1}, {iterator = 'GE'})
 ---
 ...
--- Prepared transaction is blocked due to wal_delay.
--- Start iterator with vlsn = INT64_MAX
-f2 = fiber.create(iterate_in_read_view)
+state, value = gen(param, state)
 ---
 ...
-last_read
+value
 ---
-- [1]
+- [1, 1]
 ...
--- Finish prepared transaction and send to read view the iterator.
 errinj.set("ERRINJ_WAL_DELAY", false)
 ---
 - ok
 ...
-while f1:status() ~= 'dead' do fiber.sleep(0.01) end
+while wait_replace do fiber.sleep(0.01) end
 ---
 ...
-f2:wakeup()
+state, value = gen(param, state)
 ---
 ...
-while f2:status() ~= 'dead' do fiber.sleep(0.01) end
+value
 ---
+- [2, 0]
 ...
-last_read
----
-- [2]
-...
-space:drop()
----
-...
---
--- Space drop in the middle of dump.
---
-test_run:cmd("create server test with script='vinyl/low_quota.lua'")
----
-- true
-...
-test_run:cmd("start server test with args='1048576'")
----
-- true
-...
-test_run:cmd('switch test')
----
-- true
-...
-fiber = require 'fiber'
----
-...
-box.cfg{vinyl_timeout = 0.001}
----
-...
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('i1', {parts = {1, 'unsigned'}})
----
-...
-_ = s:create_index('i2', {parts = {2, 'unsigned'}})
----
-...
-_ = s:insert{1, 1}
----
-...
--- Delay dump so that we can manage to drop the space
--- while it is still being dumped.
-box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', true)
----
-- ok
-...
--- Before failing on quota timeout, the following fiber
--- will trigger dump due to memory shortage.
-_ = fiber.create(function() s:insert{2, 2, string.rep('x', box.cfg.vinyl_memory)} end)
----
-...
--- Let the fiber run.
-fiber.sleep(0)
----
-...
--- Drop the space while the dump task is still running.
-s:drop()
----
-...
--- Wait for the dump task to complete.
-box.error.injection.set('ERRINJ_VY_RUN_WRITE_DELAY', false)
----
-- ok
-...
-box.snapshot()
----
-- ok
-...
---
--- Check that all dump/compact tasks that are in progress at
--- the time when the server stops are aborted immediately.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('i1', {parts = {1, 'unsigned'}})
----
-...
-_ = s:create_index('i2', {parts = {2, 'unsigned'}})
----
-...
-box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01)
----
-- ok
-...
-for i = 1, 1000 do s:replace{i, i} end
----
-...
-_ = fiber.create(function() box.snapshot() end)
----
-...
-fiber.sleep(0.01)
----
-...
-test_run:cmd('switch default')
----
-- true
-...
-t1 = fiber.time()
----
-...
-test_run:cmd("stop server test")
----
-- true
-...
-t2 = fiber.time()
----
-...
-t2 - t1 < 1
----
-- true
-...
-test_run:cmd("cleanup server test")
----
-- true
-...
---
--- If we logged an index creation in the metadata log before WAL write,
--- WAL failure would result in leaving the index record in vylog forever.
--- Since we use LSN to identify indexes in vylog, retrying index creation
--- would then lead to a duplicate index id in vylog and hence inability
--- to make a snapshot or recover.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-errinj.set('ERRINJ_WAL_IO', true)
----
-- ok
-...
-_ = s:create_index('pk')
----
-- error: Failed to write to disk
-...
-errinj.set('ERRINJ_WAL_IO', false)
----
-- ok
-...
-_ = s:create_index('pk')
----
-...
-box.snapshot()
----
-- ok
-...
-s:drop()
----
-...
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('i1', {parts = {1, 'unsigned'}})
----
-...
-c = 10
----
-...
-errinj.set("ERRINJ_WAL_WRITE_DISK", true)
----
-- ok
-...
-for i = 1,10 do fiber.create(function() pcall(s.replace, s, {i}) c = c - 1 end) end
----
-...
-while c ~= 0 do fiber.sleep(0.001) end
----
-...
-s:select{}
----
-- []
-...
-errinj.set("ERRINJ_WAL_WRITE_DISK", false)
----
-- ok
-...
-s:drop()
----
-...
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('i1', {parts = {1, 'unsigned'}})
----
-...
-for i = 0, 9 do s:replace({i, i + 1}) end
----
-...
-box.snapshot()
----
-- ok
-...
-errinj.set("ERRINJ_XLOG_GARBAGE", true)
----
-- ok
-...
-s:select()
----
-- error: tx checksum mismatch
-...
-errinj.set("ERRINJ_XLOG_GARBAGE", false)
----
-- ok
-...
-errinj.set("ERRINJ_VYRUN_DATA_READ", true)
----
-- ok
-...
-s:select()
----
-- error: failed to read from file
-...
-errinj.set("ERRINJ_VYRUN_DATA_READ", false)
----
-- ok
-...
-s:select()
----
-- - [0, 1]
-  - [1, 2]
-  - [2, 3]
-  - [3, 4]
-  - [4, 5]
-  - [5, 6]
-  - [6, 7]
-  - [7, 8]
-  - [8, 9]
-  - [9, 10]
-...
-s:drop()
----
-...
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('i1', {parts = {1, 'unsigned'}})
----
-...
-for i = 0, 9 do s:replace({i, i + 1}) end
----
-...
-errinj.set("ERRINJ_XLOG_GARBAGE", true)
----
-- ok
-...
-box.snapshot()
----
-- error: tx checksum mismatch
-...
-for i = 10, 19 do s:replace({i, i + 1}) end
----
-...
-errinj.set("ERRINJ_XLOG_GARBAGE", false)
----
-- ok
-...
-box.snapshot()
----
-- ok
-...
-s:select()
----
-- - [0, 1]
-  - [1, 2]
-  - [2, 3]
-  - [3, 4]
-  - [4, 5]
-  - [5, 6]
-  - [6, 7]
-  - [7, 8]
-  - [8, 9]
-  - [9, 10]
-  - [10, 11]
-  - [11, 12]
-  - [12, 13]
-  - [13, 14]
-  - [14, 15]
-  - [15, 16]
-  - [16, 17]
-  - [17, 18]
-  - [18, 19]
-  - [19, 20]
-...
-s:drop()
----
-...
--- Point select from secondary index during snapshot.
--- Once upon time that leaded to crash.
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-i1 = s:create_index('pk', {parts = {1, 'uint'}, bloom_fpr = 0.5})
----
-...
-i2 = s:create_index('sk', {parts = {2, 'uint'}, bloom_fpr = 0.5})
----
-...
-for i = 1,10 do s:replace{i, i, 0} end
----
-...
-test_run:cmd("setopt delimiter ';'")
----
-- true
-...
-function worker()
-    for i = 11,20,2 do
-        s:upsert({i, i}, {{'=', 3, 1}})
-        errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true)
-        i1:select{i}
-        s:upsert({i + 1 ,i + 1}, {{'=', 3, 1}})
-        errinj.set("ERRINJ_VY_POINT_ITER_WAIT", true)
-        i2:select{i + 1}
-    end
-end
-test_run:cmd("setopt delimiter ''");
----
-...
-f = fiber.create(worker)
----
-...
-while f:status() ~= 'dead' do box.snapshot() fiber.sleep(0.01) end
----
-...
-errinj.set("ERRINJ_VY_POINT_ITER_WAIT", false)
----
-- ok
-...
-s:drop()
----
-...
--- vinyl: vy_cache_add: Assertion `0' failed
--- https://github.com/tarantool/tarantool/issues/2685
-s = box.schema.create_space('test', {engine = 'vinyl'})
----
-...
-pk = s:create_index('pk')
----
-...
-s:replace{2, 0}
----
-- [2, 0]
-...
-box.snapshot()
----
-- ok
-...
-s:replace{1, 0}
----
-- [1, 0]
-...
-box.snapshot()
----
-- ok
-...
-s:replace{0, 0}
----
-- [0, 0]
-...
-s:select{0}
----
-- - [0, 0]
-...
-errinj.set("ERRINJ_WAL_DELAY", true)
----
-- ok
-...
-wait_replace = true
----
-...
-_ = fiber.create(function() s:replace{1, 1} wait_replace = false end)
----
-...
-gen,param,state = s:pairs({1}, {iterator = 'GE'})
----
-...
-state, value = gen(param, state)
----
-...
-value
----
-- [1, 1]
-...
-errinj.set("ERRINJ_WAL_DELAY", false)
----
-- ok
-...
-while wait_replace do fiber.sleep(0.01) end
----
-...
-state, value = gen(param, state)
----
-...
-value
----
-- [2, 0]
-...
-s:drop()
----
-...
---
--- gh-2442: secondary index cursor must skip key update, made
--- after the secondary index scan, but before a primary index
--- lookup. It is ok, and the test checks this.
---
-s = box.schema.create_space('test', {engine = 'vinyl'})
----
-...
-pk = s:create_index('pk')
----
-...
-sk = s:create_index('sk', {parts = {{2, 'unsigned'}}})
----
-...
-s:replace{1, 1}
----
-- [1, 1]
-...
-s:replace{3, 3}
----
-- [3, 3]
-...
-box.snapshot()
----
-- ok
-...
-ret = nil
----
-...
-function do_read() ret = sk:select({2}, {iterator = 'GE'}) end
----
-...
-errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", true)
----
-- ok
-...
-f = fiber.create(do_read)
----
-...
-f:status()
----
-- suspended
-...
-ret
----
-- null
-...
-s:replace{2, 2}
----
-- [2, 2]
-...
-errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", false)
----
-- ok
-...
-while ret == nil do fiber.sleep(0.01) end
----
-...
-ret
----
-- - [3, 3]
-...
-s:drop()
----
-...
---
--- gh-3412 - assertion failure at exit in case:
--- * there is a fiber waiting for quota
--- * there is a pending vylog write
---
-test_run:cmd("create server low_quota with script='vinyl/low_quota.lua'")
----
-- true
-...
-test_run:cmd("start server low_quota with args='1048576'")
----
-- true
-...
-test_run:cmd('switch low_quota')
----
-- true
-...
-_ = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = box.space.test:create_index('pk')
----
-...
-box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01)
----
-- ok
-...
-fiber = require('fiber')
----
-...
-pad = string.rep('x', 100 * 1024)
----
-...
-_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end)
----
-...
-repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len()
----
-...
-test_run:cmd("restart server low_quota with args='1048576'")
-box.error.injection.set('ERRINJ_VY_LOG_FLUSH_DELAY', true)
----
-- ok
-...
-fiber = require('fiber')
----
-...
-pad = string.rep('x', 100 * 1024)
----
-...
-_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end)
----
-...
-repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len()
----
-...
-test_run:cmd('switch default')
----
-- true
-...
-test_run:cmd("stop server low_quota")
----
-- true
-...
-test_run:cmd("cleanup server low_quota")
----
-- true
-...
---
--- Check that ALTER is abroted if a tuple inserted during space
--- format change does not conform to the new format.
---
-format = {}
----
-...
-format[1] = {name = 'field1', type = 'unsigned'}
----
-...
-format[2] = {name = 'field2', type = 'string', is_nullable = true}
----
-...
-s = box.schema.space.create('test', {engine = 'vinyl', format = format})
----
-...
-_ = s:create_index('pk', {page_size = 16})
----
-...
-pad = string.rep('x', 16)
----
-...
-for i = 101, 200 do s:replace{i, pad} end
----
-...
-box.snapshot()
----
-- ok
-...
-ch = fiber.channel(1)
----
-...
-test_run:cmd("setopt delimiter ';'")
----
-- true
-...
-_ = fiber.create(function()
-    fiber.sleep(0.01)
-    for i = 1, 100 do
-        s:replace{i, box.NULL}
-    end
-    ch:put(true)
-end);
----
-...
-test_run:cmd("setopt delimiter ''");
----
-- true
-...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
----
-- ok
-...
-format[2].is_nullable = false
----
-...
-s:format(format) -- must fail
----
-- error: 'Tuple field 2 type does not match one required by operation: expected string'
-...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
----
-- ok
-...
-ch:get()
----
-- true
-...
-s:count() -- 200
----
-- 200
-...
-s:drop()
----
-...
---
--- gh-3437: if compaction races with checkpointing, it may remove
--- files needed for backup.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('pk', {run_count_per_level = 1})
----
-...
--- Create a run file.
-_ = s:replace{1}
----
-...
-box.snapshot()
----
-- ok
-...
--- Create another run file. This will trigger compaction
--- as run_count_per_level is set to 1. Due to the error
--- injection compaction will finish before snapshot.
-_ = s:replace{2}
----
-...
-errinj.set('ERRINJ_SNAP_COMMIT_DELAY', true)
----
-- ok
-...
-c = fiber.channel(1)
----
-...
-_ = fiber.create(function() box.snapshot() c:put(true) end)
----
-...
-while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end
----
-...
-errinj.set('ERRINJ_SNAP_COMMIT_DELAY', false)
----
-- ok
-...
-c:get()
----
-- true
-...
--- Check that all files corresponding to the last checkpoint
--- are present.
-files = box.backup.start()
----
-...
-missing = {}
----
-...
-for _, f in pairs(files) do if not fio.path.exists(f) then table.insert(missing, f) end end
----
-...
-missing
----
-- []
-...
-box.backup.stop()
----
-...
-s:drop()
----
-...
---
--- gh-2449: change 'unique' index property from true to false
--- is done without index rebuild.
---
-s = box.schema.space.create('test', { engine = 'vinyl' })
----
-...
-_ = s:create_index('primary')
----
-...
-_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}})
----
-...
-s:insert{1, 10}
----
-- [1, 10]
-...
-box.snapshot()
----
-- ok
-...
-errinj.set("ERRINJ_VY_READ_PAGE", true);
----
-- ok
-...
-s.index.secondary:alter{unique = false} -- ok
----
-...
-s.index.secondary.unique
----
-- false
-...
-s.index.secondary:alter{unique = true} -- error
----
-- error: Error injection 'vinyl page read'
-...
-s.index.secondary.unique
----
-- false
-...
-errinj.set("ERRINJ_VY_READ_PAGE", false);
----
-- ok
-...
-s:insert{2, 10}
----
-- [2, 10]
-...
-s.index.secondary:select(10)
----
-- - [1, 10]
-  - [2, 10]
-...
-s:drop()
----
-...
---
--- Check that ALTER is aborted if a tuple inserted during index build
--- doesn't conform to the new format.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('pk', {page_size = 16})
----
-...
-pad = string.rep('x', 16)
----
-...
-for i = 101, 200 do s:replace{i, i, pad} end
----
-...
-box.snapshot()
----
-- ok
-...
-ch = fiber.channel(1)
----
-...
-test_run:cmd("setopt delimiter ';'")
----
-- true
-...
-_ = fiber.create(function()
-    fiber.sleep(0.01)
-    for i = 1, 100 do
-        s:replace{i}
-    end
-    ch:put(true)
-end);
----
-...
-test_run:cmd("setopt delimiter ''");
----
-- true
-...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
----
-- ok
-...
-s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
----
-- error: Tuple field 2 required by space format is missing
-...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
----
-- ok
-...
-ch:get()
----
-- true
-...
-s:count() -- 200
----
-- 200
-...
-s:drop()
----
-...
---
--- Check that ALTER is aborted if a tuple inserted during index build
--- violates unique constraint.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('pk', {page_size = 16})
----
-...
-pad = string.rep('x', 16)
----
-...
-for i = 101, 200 do s:replace{i, i, pad} end
----
-...
-box.snapshot()
----
-- ok
-...
-ch = fiber.channel(1)
----
-...
-test_run:cmd("setopt delimiter ';'")
----
-- true
-...
-_ = fiber.create(function()
-    fiber.sleep(0.01)
-    for i = 1, 100 do
-        s:replace{i, i + 1}
-    end
-    ch:put(true)
-end);
----
-...
-test_run:cmd("setopt delimiter ''");
----
-- true
-...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
----
-- ok
-...
-s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
----
-- error: Duplicate key exists in unique index 'sk' in space 'test'
-...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
----
-- ok
-...
-ch:get()
----
-- true
-...
-s:count() -- 200
----
-- 200
-...
-s:drop()
----
-...
---
--- Check that modifications done to the space during the final dump
--- of a newly built index are recovered properly.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
----
-...
-_ = s:create_index('pk')
----
-...
-for i = 1, 5 do s:replace{i, i} end
----
-...
-errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true)
----
-- ok
-...
-ch = fiber.channel(1)
----
-...
-_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end)
----
-...
-fiber.sleep(0.01)
----
-...
-_ = s:delete{1}
----
-...
-_ = s:replace{2, -2}
----
-...
-_ = s:delete{2}
----
-...
-_ = s:replace{3, -3}
----
-...
-_ = s:replace{3, -2}
----
-...
-_ = s:replace{3, -1}
----
-...
-_ = s:delete{3}
----
-...
-_ = s:upsert({3, 3}, {{'=', 2, 1}})
----
-...
-_ = s:upsert({3, 3}, {{'=', 2, 2}})
----
-...
-_ = s:delete{3}
----
-...
-_ = s:replace{4, -1}
----
-...
-_ = s:replace{4, -2}
----
-...
-_ = s:replace{4, -4}
----
-...
-_ = s:upsert({5, 1}, {{'=', 2, 1}})
----
-...
-_ = s:upsert({5, 2}, {{'=', 2, -5}})
----
-...
-_ = s:replace{6, -6}
----
-...
-_ = s:upsert({7, -7}, {{'=', 2, -7}})
----
-...
-errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false)
----
-- ok
-...
-ch:get()
----
-- true
-...
-s.index.sk:select()
----
-- - [7, -7]
-  - [6, -6]
-  - [5, -5]
-  - [4, -4]
-...
-s.index.sk:stat().memory.rows
----
-- 27
-...
-test_run:cmd('restart server default')
-s = box.space.test
----
-...
-s.index.sk:select()
----
-- - [7, -7]
-  - [6, -6]
-  - [5, -5]
-  - [4, -4]
-...
-s.index.sk:stat().memory.rows
----
-- 27
-...
-box.snapshot()
----
-- ok
-...
-s.index.sk:select()
----
-- - [7, -7]
-  - [6, -6]
-  - [5, -5]
-  - [4, -4]
-...
-s.index.sk:stat().memory.rows
----
-- 0
-...
-s:drop()
+s:drop()
 ---
 ...
 --
--- Check that tarantool doesn't hang or crash if error
--- occurs while writing a deferred DELETE to WAL.
+-- gh-2442: secondary index cursor must skip key update, made
+-- after the secondary index scan, but before a primary index
+-- lookup. It is ok, and the test checks this.
 --
-fiber = require('fiber')
----
-...
-errinj = box.error.injection
----
-...
-s = box.schema.space.create('test', {engine = 'vinyl'})
+s = box.schema.create_space('test', {engine = 'vinyl'})
 ---
 ...
-_ = s:create_index('pk', {run_count_per_level = 10})
+pk = s:create_index('pk')
 ---
 ...
-_ = s:create_index('sk', {unique = false, parts = {2, 'unsigned'}})
+sk = s:create_index('sk', {parts = {{2, 'unsigned'}}})
 ---
 ...
-s:replace{1, 10}
+s:replace{1, 1}
 ---
-- [1, 10]
+- [1, 1]
 ...
-s:replace{10, 100} -- to prevent last-level compaction (gh-3657)
+s:replace{3, 3}
 ---
-- [10, 100]
+- [3, 3]
 ...
 box.snapshot()
 ---
 - ok
 ...
-s:replace{1, 20}
----
-- [1, 20]
-...
-box.snapshot()
+ret = nil
 ---
-- ok
 ...
-errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.001)
+function do_read() ret = sk:select({2}, {iterator = 'GE'}) end
 ---
-- ok
 ...
-errinj.set("ERRINJ_WAL_IO", true)
+errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", true)
 ---
 - ok
 ...
-errors = box.stat.ERROR.total
+f = fiber.create(do_read)
 ---
 ...
-s.index.pk:compact()
+f:status()
 ---
+- suspended
 ...
-while box.stat.ERROR.total - errors == 0 do fiber.sleep(0.001) end
+ret
 ---
+- null
 ...
-s.index.pk:stat().disk.compact.count -- 0
+s:replace{2, 2}
 ---
-- 0
+- [2, 2]
 ...
-errinj.set("ERRINJ_WAL_IO", false)
+errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", false)
 ---
 - ok
 ...
-while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end
----
-...
-s.index.pk:stat().disk.compact.count -- 1
----
-- 1
-...
-errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0)
+while ret == nil do fiber.sleep(0.01) end
 ---
-- ok
 ...
-box.snapshot() -- ok
+ret
 ---
-- ok
+- - [3, 3]
 ...
 s:drop()
 ---
 ...
 --
--- gh-3458: check that rw transactions that started before DDL are
--- aborted.
+-- gh-3412 - assertion failure at exit in case:
+-- * there is a fiber waiting for quota
+-- * there is a pending vylog write
 --
-vinyl_cache = box.cfg.vinyl_cache
----
-...
-box.cfg{vinyl_cache = 0}
----
-...
-s1 = box.schema.space.create('test1', {engine = 'vinyl'})
----
-...
-_ = s1:create_index('pk', {page_size = 16})
----
-...
-s2 = box.schema.space.create('test2', {engine = 'vinyl'})
----
-...
-_ = s2:create_index('pk')
----
-...
-pad = string.rep('x', 16)
----
-...
-for i = 101, 200 do s1:replace{i, i, pad} end
----
-...
-box.snapshot()
----
-- ok
-...
-test_run:cmd("setopt delimiter ';'")
+test_run:cmd("create server low_quota with script='vinyl/low_quota.lua'")
 ---
 - true
 ...
-function async_replace(space, tuple, timeout)
-    local c = fiber.channel(1)
-    fiber.create(function()
-        box.begin()
-        space:replace(tuple)
-        fiber.sleep(timeout)
-        local status = pcall(box.commit)
-        c:put(status)
-    end)
-    return c
-end;
----
-...
-test_run:cmd("setopt delimiter ''");
+test_run:cmd("start server low_quota with args='1048576'")
 ---
 - true
 ...
-c1 = async_replace(s1, {1}, 0.01)
----
-...
-c2 = async_replace(s2, {1}, 0.01)
+test_run:cmd('switch low_quota')
 ---
+- true
 ...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+_ = box.schema.space.create('test', {engine = 'vinyl'})
 ---
-- ok
 ...
-s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}}
+_ = box.space.test:create_index('pk')
 ---
 ...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+box.error.injection.set('ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT', 0.01)
 ---
 - ok
 ...
-c1:get() -- false (transaction was aborted)
----
-- false
-...
-c2:get() -- true
----
-- true
-...
-s1:get(1) == nil
----
-- true
-...
-s2:get(1) ~= nil
----
-- true
-...
-s1:format()
+fiber = require('fiber')
 ---
-- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'unsigned'}]
 ...
-s1:format{}
+pad = string.rep('x', 100 * 1024)
 ---
 ...
-c1 = async_replace(s1, {2}, 0.01)
+_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end)
 ---
 ...
-c2 = async_replace(s2, {2}, 0.01)
+repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len()
 ---
 ...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+test_run:cmd("restart server low_quota with args='1048576'")
+box.error.injection.set('ERRINJ_VY_LOG_FLUSH_DELAY', true)
 ---
 - ok
 ...
-_ = s1:create_index('sk', {parts = {2, 'unsigned'}})
+fiber = require('fiber')
 ---
 ...
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+pad = string.rep('x', 100 * 1024)
 ---
-- ok
 ...
-c1:get() -- false (transaction was aborted)
+_ = fiber.create(function() for i = 1, 11 do box.space.test:replace{i, pad} end end)
 ---
-- false
 ...
-c2:get() -- true
+repeat fiber.sleep(0.001) until box.cfg.vinyl_memory - box.stat.vinyl().memory.level0 < pad:len()
 ---
-- true
 ...
-s1:get(2) == nil
+test_run:cmd('switch default')
 ---
 - true
 ...
-s2:get(2) ~= nil
+test_run:cmd("stop server low_quota")
 ---
 - true
 ...
-s1.index.pk:count() == s1.index.sk:count()
+test_run:cmd("cleanup server low_quota")
 ---
 - true
 ...
-s1:drop()
----
-...
-s2:drop()
----
-...
-box.cfg{vinyl_cache = vinyl_cache}
+--
+-- gh-3437: if compaction races with checkpointing, it may remove
+-- files needed for backup.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
 ...
--- Transactions that reached WAL must not be aborted.
-s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('pk', {run_count_per_level = 1})
 ---
 ...
-_ = s:create_index('pk')
+-- Create a run file.
+_ = s:replace{1}
 ---
 ...
-errinj.set('ERRINJ_WAL_DELAY', true)
+box.snapshot()
 ---
 - ok
 ...
-_ = fiber.create(function() s:replace{1} end)
+-- Create another run file. This will trigger compaction
+-- as run_count_per_level is set to 1. Due to the error
+-- injection compaction will finish before snapshot.
+_ = s:replace{2}
 ---
 ...
-_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
+errinj.set('ERRINJ_SNAP_COMMIT_DELAY', true)
 ---
+- ok
 ...
-fiber.sleep(0)
+c = fiber.channel(1)
 ---
 ...
-s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail
+_ = fiber.create(function() box.snapshot() c:put(true) end)
 ---
-- error: Tuple field 2 required by space format is missing
 ...
-s:select()
+while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end
 ---
-- - [1]
 ...
-s:truncate()
+errinj.set('ERRINJ_SNAP_COMMIT_DELAY', false)
 ---
+- ok
 ...
-errinj.set('ERRINJ_WAL_DELAY', true)
+c:get()
 ---
-- ok
+- true
 ...
-_ = fiber.create(function() s:replace{1} end)
+-- Check that all files corresponding to the last checkpoint
+-- are present.
+files = box.backup.start()
 ---
 ...
-_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
+missing = {}
 ---
 ...
-fiber.sleep(0)
+for _, f in pairs(files) do if not fio.path.exists(f) then table.insert(missing, f) end end
 ---
 ...
-s:create_index('sk', {parts = {2, 'unsigned'}})
+missing
 ---
-- error: Tuple field 2 required by space format is missing
+- []
 ...
-s:select()
+box.backup.stop()
 ---
-- - [1]
 ...
 s:drop()
 ---
 ...
 --
--- Check disk.compact.queue stat.
+-- Check that tarantool doesn't hang or crash if error
+-- occurs while writing a deferred DELETE to WAL.
 --
-test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: <bytes_compressed>'")
+fiber = require('fiber')
 ---
-- true
 ...
-s = box.schema.space.create('test', {engine = 'vinyl'})
+errinj = box.error.injection
 ---
 ...
-i = s:create_index('pk', {run_count_per_level = 2})
+s = box.schema.space.create('test', {engine = 'vinyl'})
 ---
 ...
-function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end
+_ = s:create_index('pk', {run_count_per_level = 10})
 ---
 ...
-dump()
+_ = s:create_index('sk', {unique = false, parts = {2, 'unsigned'}})
 ---
 ...
-i:stat().disk.compact.queue -- none
+s:replace{1, 10}
 ---
-- bytes_compressed: <bytes_compressed>
-  pages: 0
-  rows: 0
-  bytes: 0
+- [1, 10]
 ...
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+s:replace{10, 100} -- to prevent last-level compaction (gh-3657)
 ---
-- true
+- [10, 100]
 ...
-errinj.set('ERRINJ_VY_COMPACTION_DELAY', true)
+box.snapshot()
 ---
 - ok
 ...
-dump()
----
-...
-dump()
----
-...
-i:stat().disk.compact.queue -- 30 statements
+s:replace{1, 20}
 ---
-- bytes_compressed: <bytes_compressed>
-  pages: 3
-  rows: 30
-  bytes: 471
+- [1, 20]
 ...
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+box.snapshot()
 ---
-- true
+- ok
 ...
-dump()
+errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.001)
 ---
+- ok
 ...
-i:stat().disk.compact.queue -- 40 statements
+errinj.set("ERRINJ_WAL_IO", true)
 ---
-- bytes_compressed: <bytes_compressed>
-  pages: 4
-  rows: 40
-  bytes: 628
+- ok
 ...
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+errors = box.stat.ERROR.total
 ---
-- true
 ...
-dump()
+s.index.pk:compact()
 ---
 ...
-i:stat().disk.compact.queue -- 50 statements
+while box.stat.ERROR.total - errors == 0 do fiber.sleep(0.001) end
 ---
-- bytes_compressed: <bytes_compressed>
-  pages: 5
-  rows: 50
-  bytes: 785
 ...
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+s.index.pk:stat().disk.compact.count -- 0
 ---
-- true
+- 0
 ...
-box.stat.reset() -- doesn't affect queue size
+errinj.set("ERRINJ_WAL_IO", false)
 ---
+- ok
 ...
-i:stat().disk.compact.queue -- 50 statements
+while s.index.pk:stat().disk.compact.count == 0 do fiber.sleep(0.001) end
 ---
-- bytes_compressed: <bytes_compressed>
-  pages: 5
-  rows: 50
-  bytes: 785
 ...
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+s.index.pk:stat().disk.compact.count -- 1
 ---
-- true
+- 1
 ...
-errinj.set('ERRINJ_VY_COMPACTION_DELAY', false)
+errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0)
 ---
 - ok
 ...
-while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end
----
-...
-i:stat().disk.compact.queue -- none
+box.snapshot() -- ok
 ---
-- bytes_compressed: <bytes_compressed>
-  pages: 0
-  rows: 0
-  bytes: 0
+- ok
 ...
 s:drop()
 ---
 ...
-test_run:cmd("clear filter")
----
-- true
-...
 --
 -- Check that an instance doesn't crash if a run file needed for
 -- joining a replica is corrupted (see gh-3708).
diff --git a/test/vinyl/errinj.test.lua b/test/vinyl/errinj.test.lua
index 59e9f81c6d9327d99b302c59d414003be2db073b..d4590fb420211ceb9c7b82c34171e270c74393c9 100644
--- a/test/vinyl/errinj.test.lua
+++ b/test/vinyl/errinj.test.lua
@@ -1,21 +1,8 @@
---
--- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE
---
 test_run = require('test_run').new()
 fio = require('fio')
 fiber = require('fiber')
 errinj = box.error.injection
 errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0.040)
-s = box.schema.space.create('test', {engine='vinyl'})
-_ = s:create_index('pk')
-function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end
-errinj.set("ERRINJ_WAL_WRITE", true)
-f()
-s:select{}
-errinj.set("ERRINJ_WAL_WRITE", false)
-f()
-s:select{}
-s:drop()
 --
 -- Lost data in case of dump error
 --
@@ -165,186 +152,6 @@ s:drop() -- index is gone
 fiber.sleep(0.05)
 errinj.set("ERRINJ_VY_SQUASH_TIMEOUT", 0)
 
---https://github.com/tarantool/tarantool/issues/1842
---test error injection
-s = box.schema.space.create('test', {engine='vinyl'})
-_ = s:create_index('pk')
-s:replace{0, 0}
-
-s:replace{1, 0}
-s:replace{2, 0}
-errinj.set("ERRINJ_WAL_WRITE", true)
-s:replace{3, 0}
-s:replace{4, 0}
-s:replace{5, 0}
-s:replace{6, 0}
-errinj.set("ERRINJ_WAL_WRITE", false)
-s:replace{7, 0}
-s:replace{8, 0}
-s:select{}
-
-s:drop()
-
-create_iterator = require('utils').create_iterator
-
---iterator test
-test_run:cmd("setopt delimiter ';'")
-
-fiber_status = 0
-
-function fiber_func()
-    box.begin()
-    s:replace{5, 5}
-    fiber_status = 1
-    local res = {pcall(box.commit) }
-    fiber_status = 2
-    return unpack(res)
-end;
-
-test_run:cmd("setopt delimiter ''");
-
-s = box.schema.space.create('test', {engine='vinyl'})
-_ = s:create_index('pk')
-fiber = require('fiber')
-
-_ = s:replace{0, 0}
-_ = s:replace{10, 0}
-_ = s:replace{20, 0}
-
-test_run:cmd("setopt delimiter ';'");
-
-faced_trash = false
-for i = 1,100 do
-    errinj.set("ERRINJ_WAL_WRITE", true)
-    local f = fiber.create(fiber_func)
-    local itr = create_iterator(s, {0}, {iterator='GE'})
-    local first = itr.next()
-    local second = itr.next()
-    if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end
-    while fiber_status <= 1 do fiber.sleep(0.001) end
-    local _,next = pcall(itr.next)
-    _,next = pcall(itr.next)
-    _,next = pcall(itr.next)
-    errinj.set("ERRINJ_WAL_WRITE", false)
-    s:delete{5}
-end;
-
-test_run:cmd("setopt delimiter ''");
-
-faced_trash
-
-s:drop()
-
--- TX in prepared but not committed state
-s = box.schema.space.create('test', {engine='vinyl'})
-_ = s:create_index('pk')
-fiber = require('fiber')
-txn_proxy = require('txn_proxy')
-
-s:replace{1, "original"}
-s:replace{2, "original"}
-s:replace{3, "original"}
-
-c0 = txn_proxy.new()
-c0:begin()
-c1 = txn_proxy.new()
-c1:begin()
-c2 = txn_proxy.new()
-c2:begin()
-c3 = txn_proxy.new()
-c3:begin()
-
---
--- Prepared transactions
---
-
--- Pause WAL writer to cause all further calls to box.commit() to move
--- transactions into prepared, but not committed yet state.
-errinj.set("ERRINJ_WAL_DELAY", true)
-lsn = box.info.lsn
-c0('s:replace{1, "c0"}')
-c0('s:replace{2, "c0"}')
-c0('s:replace{3, "c0"}')
-_ = fiber.create(c0.commit, c0)
-box.info.lsn == lsn
-c1('s:replace{1, "c1"}')
-c1('s:replace{2, "c1"}')
-_ = fiber.create(c1.commit, c1)
-box.info.lsn == lsn
-c3('s:select{1}') -- c1 is visible
-c2('s:replace{1, "c2"}')
-c2('s:replace{3, "c2"}')
-_ = fiber.create(c2.commit, c2)
-box.info.lsn == lsn
-c3('s:select{1}') -- c1 is visible, c2 is not
-c3('s:select{2}') -- c1 is visible
-c3('s:select{3}') -- c2 is not visible
-
--- Resume WAL writer and wait until all transactions will been committed
-errinj.set("ERRINJ_WAL_DELAY", false)
-REQ_COUNT = 7
-while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end
-box.info.lsn == lsn + REQ_COUNT
-
-c3('s:select{1}') -- c1 is visible, c2 is not
-c3('s:select{2}') -- c1 is visible
-c3('s:select{3}') -- c2 is not visible
-c3:commit()
-
-s:drop()
-
---
--- Test mem restoration on a prepared and not commited statement
--- after moving iterator into read view.
---
-space = box.schema.space.create('test', {engine = 'vinyl'})
-pk = space:create_index('pk')
-space:replace{1}
-space:replace{2}
-space:replace{3}
-
-last_read = nil
-
-errinj.set("ERRINJ_WAL_DELAY", true)
-
-test_run:cmd("setopt delimiter ';'")
-
-function fill_space()
-    box.begin()
-    space:replace{1}
-    space:replace{2}
-    space:replace{3}
--- block until wal_delay = false
-    box.commit()
--- send iterator to read view
-    space:replace{1, 1}
--- flush mem and update index version to trigger iterator restore
-    box.snapshot()
-end;
-
-function iterate_in_read_view()
-    local i = create_iterator(space)
-    last_read = i.next()
-    fiber.sleep(100000)
-    last_read = i.next()
-end;
-
-test_run:cmd("setopt delimiter ''");
-
-f1 = fiber.create(fill_space)
--- Prepared transaction is blocked due to wal_delay.
--- Start iterator with vlsn = INT64_MAX
-f2 = fiber.create(iterate_in_read_view)
-last_read
--- Finish prepared transaction and send to read view the iterator.
-errinj.set("ERRINJ_WAL_DELAY", false)
-while f1:status() ~= 'dead' do fiber.sleep(0.01) end
-f2:wakeup()
-while f2:status() ~= 'dead' do fiber.sleep(0.01) end
-last_read
-
-space:drop()
-
 --
 -- Space drop in the middle of dump.
 --
@@ -389,21 +196,6 @@ t2 = fiber.time()
 t2 - t1 < 1
 test_run:cmd("cleanup server test")
 
---
--- If we logged an index creation in the metadata log before WAL write,
--- WAL failure would result in leaving the index record in vylog forever.
--- Since we use LSN to identify indexes in vylog, retrying index creation
--- would then lead to a duplicate index id in vylog and hence inability
--- to make a snapshot or recover.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
-errinj.set('ERRINJ_WAL_IO', true)
-_ = s:create_index('pk')
-errinj.set('ERRINJ_WAL_IO', false)
-_ = s:create_index('pk')
-box.snapshot()
-s:drop()
-
 s = box.schema.space.create('test', {engine = 'vinyl'})
 _ = s:create_index('i1', {parts = {1, 'unsigned'}})
 
@@ -540,41 +332,6 @@ test_run:cmd('switch default')
 test_run:cmd("stop server low_quota")
 test_run:cmd("cleanup server low_quota")
 
---
--- Check that ALTER is abroted if a tuple inserted during space
--- format change does not conform to the new format.
---
-format = {}
-format[1] = {name = 'field1', type = 'unsigned'}
-format[2] = {name = 'field2', type = 'string', is_nullable = true}
-s = box.schema.space.create('test', {engine = 'vinyl', format = format})
-_ = s:create_index('pk', {page_size = 16})
-
-pad = string.rep('x', 16)
-for i = 101, 200 do s:replace{i, pad} end
-box.snapshot()
-
-ch = fiber.channel(1)
-test_run:cmd("setopt delimiter ';'")
-_ = fiber.create(function()
-    fiber.sleep(0.01)
-    for i = 1, 100 do
-        s:replace{i, box.NULL}
-    end
-    ch:put(true)
-end);
-test_run:cmd("setopt delimiter ''");
-
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
-format[2].is_nullable = false
-s:format(format) -- must fail
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
-
-ch:get()
-
-s:count() -- 200
-s:drop()
-
 --
 -- gh-3437: if compaction races with checkpointing, it may remove
 -- files needed for backup.
@@ -603,140 +360,6 @@ missing
 box.backup.stop()
 s:drop()
 
---
--- gh-2449: change 'unique' index property from true to false
--- is done without index rebuild.
---
-s = box.schema.space.create('test', { engine = 'vinyl' })
-_ = s:create_index('primary')
-_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}})
-s:insert{1, 10}
-box.snapshot()
-errinj.set("ERRINJ_VY_READ_PAGE", true);
-s.index.secondary:alter{unique = false} -- ok
-s.index.secondary.unique
-s.index.secondary:alter{unique = true} -- error
-s.index.secondary.unique
-errinj.set("ERRINJ_VY_READ_PAGE", false);
-s:insert{2, 10}
-s.index.secondary:select(10)
-s:drop()
-
---
--- Check that ALTER is aborted if a tuple inserted during index build
--- doesn't conform to the new format.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
-_ = s:create_index('pk', {page_size = 16})
-
-pad = string.rep('x', 16)
-for i = 101, 200 do s:replace{i, i, pad} end
-box.snapshot()
-
-ch = fiber.channel(1)
-test_run:cmd("setopt delimiter ';'")
-_ = fiber.create(function()
-    fiber.sleep(0.01)
-    for i = 1, 100 do
-        s:replace{i}
-    end
-    ch:put(true)
-end);
-test_run:cmd("setopt delimiter ''");
-
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
-s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
-
-ch:get()
-
-s:count() -- 200
-s:drop()
-
---
--- Check that ALTER is aborted if a tuple inserted during index build
--- violates unique constraint.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
-_ = s:create_index('pk', {page_size = 16})
-
-pad = string.rep('x', 16)
-for i = 101, 200 do s:replace{i, i, pad} end
-box.snapshot()
-
-ch = fiber.channel(1)
-test_run:cmd("setopt delimiter ';'")
-_ = fiber.create(function()
-    fiber.sleep(0.01)
-    for i = 1, 100 do
-        s:replace{i, i + 1}
-    end
-    ch:put(true)
-end);
-test_run:cmd("setopt delimiter ''");
-
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
-s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
-
-ch:get()
-
-s:count() -- 200
-s:drop()
-
---
--- Check that modifications done to the space during the final dump
--- of a newly built index are recovered properly.
---
-s = box.schema.space.create('test', {engine = 'vinyl'})
-_ = s:create_index('pk')
-
-for i = 1, 5 do s:replace{i, i} end
-
-errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true)
-ch = fiber.channel(1)
-_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end)
-
-fiber.sleep(0.01)
-
-_ = s:delete{1}
-_ = s:replace{2, -2}
-_ = s:delete{2}
-_ = s:replace{3, -3}
-_ = s:replace{3, -2}
-_ = s:replace{3, -1}
-_ = s:delete{3}
-_ = s:upsert({3, 3}, {{'=', 2, 1}})
-_ = s:upsert({3, 3}, {{'=', 2, 2}})
-_ = s:delete{3}
-_ = s:replace{4, -1}
-_ = s:replace{4, -2}
-_ = s:replace{4, -4}
-_ = s:upsert({5, 1}, {{'=', 2, 1}})
-_ = s:upsert({5, 2}, {{'=', 2, -5}})
-_ = s:replace{6, -6}
-_ = s:upsert({7, -7}, {{'=', 2, -7}})
-
-errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false)
-ch:get()
-
-s.index.sk:select()
-s.index.sk:stat().memory.rows
-
-test_run:cmd('restart server default')
-
-s = box.space.test
-
-s.index.sk:select()
-s.index.sk:stat().memory.rows
-
-box.snapshot()
-
-s.index.sk:select()
-s.index.sk:stat().memory.rows
-
-s:drop()
-
 --
 -- Check that tarantool doesn't hang or crash if error
 -- occurs while writing a deferred DELETE to WAL.
@@ -767,123 +390,6 @@ errinj.set("ERRINJ_VY_SCHED_TIMEOUT", 0)
 box.snapshot() -- ok
 s:drop()
 
---
--- gh-3458: check that rw transactions that started before DDL are
--- aborted.
---
-vinyl_cache = box.cfg.vinyl_cache
-box.cfg{vinyl_cache = 0}
-
-s1 = box.schema.space.create('test1', {engine = 'vinyl'})
-_ = s1:create_index('pk', {page_size = 16})
-s2 = box.schema.space.create('test2', {engine = 'vinyl'})
-_ = s2:create_index('pk')
-
-pad = string.rep('x', 16)
-for i = 101, 200 do s1:replace{i, i, pad} end
-box.snapshot()
-
-test_run:cmd("setopt delimiter ';'")
-function async_replace(space, tuple, timeout)
-    local c = fiber.channel(1)
-    fiber.create(function()
-        box.begin()
-        space:replace(tuple)
-        fiber.sleep(timeout)
-        local status = pcall(box.commit)
-        c:put(status)
-    end)
-    return c
-end;
-test_run:cmd("setopt delimiter ''");
-
-c1 = async_replace(s1, {1}, 0.01)
-c2 = async_replace(s2, {1}, 0.01)
-
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
-s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}}
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
-
-c1:get() -- false (transaction was aborted)
-c2:get() -- true
-
-s1:get(1) == nil
-s2:get(1) ~= nil
-s1:format()
-s1:format{}
-
-c1 = async_replace(s1, {2}, 0.01)
-c2 = async_replace(s2, {2}, 0.01)
-
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
-_ = s1:create_index('sk', {parts = {2, 'unsigned'}})
-errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
-
-c1:get() -- false (transaction was aborted)
-c2:get() -- true
-
-s1:get(2) == nil
-s2:get(2) ~= nil
-s1.index.pk:count() == s1.index.sk:count()
-
-s1:drop()
-s2:drop()
-box.cfg{vinyl_cache = vinyl_cache}
-
--- Transactions that reached WAL must not be aborted.
-s = box.schema.space.create('test', {engine = 'vinyl'})
-_ = s:create_index('pk')
-
-errinj.set('ERRINJ_WAL_DELAY', true)
-_ = fiber.create(function() s:replace{1} end)
-_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
-
-fiber.sleep(0)
-s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail
-s:select()
-s:truncate()
-
-errinj.set('ERRINJ_WAL_DELAY', true)
-_ = fiber.create(function() s:replace{1} end)
-_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
-
-fiber.sleep(0)
-s:create_index('sk', {parts = {2, 'unsigned'}})
-s:select()
-s:drop()
-
---
--- Check disk.compact.queue stat.
---
-test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: <bytes_compressed>'")
-
-s = box.schema.space.create('test', {engine = 'vinyl'})
-i = s:create_index('pk', {run_count_per_level = 2})
-function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end
-dump()
-i:stat().disk.compact.queue -- none
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
-errinj.set('ERRINJ_VY_COMPACTION_DELAY', true)
-dump()
-dump()
-i:stat().disk.compact.queue -- 30 statements
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
-dump()
-i:stat().disk.compact.queue -- 40 statements
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
-dump()
-i:stat().disk.compact.queue -- 50 statements
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
-box.stat.reset() -- doesn't affect queue size
-i:stat().disk.compact.queue -- 50 statements
-i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
-errinj.set('ERRINJ_VY_COMPACTION_DELAY', false)
-while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end
-i:stat().disk.compact.queue -- none
-s:drop()
-
-test_run:cmd("clear filter")
-
 --
 -- Check that an instance doesn't crash if a run file needed for
 -- joining a replica is corrupted (see gh-3708).
diff --git a/test/vinyl/errinj_ddl.result b/test/vinyl/errinj_ddl.result
new file mode 100644
index 0000000000000000000000000000000000000000..4f5d59cdc8f4d10982c4b919a372690058262735
--- /dev/null
+++ b/test/vinyl/errinj_ddl.result
@@ -0,0 +1,595 @@
+test_run = require('test_run').new()
+---
+...
+fiber = require('fiber')
+---
+...
+errinj = box.error.injection
+---
+...
+--
+-- Check that ALTER is abroted if a tuple inserted during space
+-- format change does not conform to the new format.
+--
+format = {}
+---
+...
+format[1] = {name = 'field1', type = 'unsigned'}
+---
+...
+format[2] = {name = 'field2', type = 'string', is_nullable = true}
+---
+...
+s = box.schema.space.create('test', {engine = 'vinyl', format = format})
+---
+...
+_ = s:create_index('pk', {page_size = 16})
+---
+...
+pad = string.rep('x', 16)
+---
+...
+for i = 101, 200 do s:replace{i, pad} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+ch = fiber.channel(1)
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+_ = fiber.create(function()
+    fiber.sleep(0.01)
+    for i = 1, 100 do
+        s:replace{i, box.NULL}
+    end
+    ch:put(true)
+end);
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+---
+- ok
+...
+format[2].is_nullable = false
+---
+...
+s:format(format) -- must fail
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected string'
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+---
+- ok
+...
+ch:get()
+---
+- true
+...
+s:count() -- 200
+---
+- 200
+...
+s:drop()
+---
+...
+--
+-- gh-2449: change 'unique' index property from true to false
+-- is done without index rebuild.
+--
+s = box.schema.space.create('test', { engine = 'vinyl' })
+---
+...
+_ = s:create_index('primary')
+---
+...
+_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}})
+---
+...
+s:insert{1, 10}
+---
+- [1, 10]
+...
+box.snapshot()
+---
+- ok
+...
+errinj.set("ERRINJ_VY_READ_PAGE", true);
+---
+- ok
+...
+s.index.secondary:alter{unique = false} -- ok
+---
+...
+s.index.secondary.unique
+---
+- false
+...
+s.index.secondary:alter{unique = true} -- error
+---
+- error: Error injection 'vinyl page read'
+...
+s.index.secondary.unique
+---
+- false
+...
+errinj.set("ERRINJ_VY_READ_PAGE", false);
+---
+- ok
+...
+s:insert{2, 10}
+---
+- [2, 10]
+...
+s.index.secondary:select(10)
+---
+- - [1, 10]
+  - [2, 10]
+...
+s:drop()
+---
+...
+--
+-- Check that ALTER is aborted if a tuple inserted during index build
+-- doesn't conform to the new format.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+_ = s:create_index('pk', {page_size = 16})
+---
+...
+pad = string.rep('x', 16)
+---
+...
+for i = 101, 200 do s:replace{i, i, pad} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+ch = fiber.channel(1)
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+_ = fiber.create(function()
+    fiber.sleep(0.01)
+    for i = 1, 100 do
+        s:replace{i}
+    end
+    ch:put(true)
+end);
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+---
+- ok
+...
+s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
+---
+- error: Tuple field 2 required by space format is missing
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+---
+- ok
+...
+ch:get()
+---
+- true
+...
+s:count() -- 200
+---
+- 200
+...
+s:drop()
+---
+...
+--
+-- Check that ALTER is aborted if a tuple inserted during index build
+-- violates unique constraint.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+_ = s:create_index('pk', {page_size = 16})
+---
+...
+pad = string.rep('x', 16)
+---
+...
+for i = 101, 200 do s:replace{i, i, pad} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+ch = fiber.channel(1)
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+_ = fiber.create(function()
+    fiber.sleep(0.01)
+    for i = 1, 100 do
+        s:replace{i, i + 1}
+    end
+    ch:put(true)
+end);
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+---
+- ok
+...
+s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
+---
+- error: Duplicate key exists in unique index 'sk' in space 'test'
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+---
+- ok
+...
+ch:get()
+---
+- true
+...
+s:count() -- 200
+---
+- 200
+...
+s:drop()
+---
+...
+--
+-- Check that modifications done to the space during the final dump
+-- of a newly built index are recovered properly.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+_ = s:create_index('pk')
+---
+...
+for i = 1, 5 do s:replace{i, i} end
+---
+...
+errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true)
+---
+- ok
+...
+ch = fiber.channel(1)
+---
+...
+_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end)
+---
+...
+fiber.sleep(0.01)
+---
+...
+_ = s:delete{1}
+---
+...
+_ = s:replace{2, -2}
+---
+...
+_ = s:delete{2}
+---
+...
+_ = s:replace{3, -3}
+---
+...
+_ = s:replace{3, -2}
+---
+...
+_ = s:replace{3, -1}
+---
+...
+_ = s:delete{3}
+---
+...
+_ = s:upsert({3, 3}, {{'=', 2, 1}})
+---
+...
+_ = s:upsert({3, 3}, {{'=', 2, 2}})
+---
+...
+_ = s:delete{3}
+---
+...
+_ = s:replace{4, -1}
+---
+...
+_ = s:replace{4, -2}
+---
+...
+_ = s:replace{4, -4}
+---
+...
+_ = s:upsert({5, 1}, {{'=', 2, 1}})
+---
+...
+_ = s:upsert({5, 2}, {{'=', 2, -5}})
+---
+...
+_ = s:replace{6, -6}
+---
+...
+_ = s:upsert({7, -7}, {{'=', 2, -7}})
+---
+...
+errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false)
+---
+- ok
+...
+ch:get()
+---
+- true
+...
+s.index.sk:select()
+---
+- - [7, -7]
+  - [6, -6]
+  - [5, -5]
+  - [4, -4]
+...
+s.index.sk:stat().memory.rows
+---
+- 27
+...
+test_run:cmd('restart server default')
+fiber = require('fiber')
+---
+...
+errinj = box.error.injection
+---
+...
+s = box.space.test
+---
+...
+s.index.sk:select()
+---
+- - [7, -7]
+  - [6, -6]
+  - [5, -5]
+  - [4, -4]
+...
+s.index.sk:stat().memory.rows
+---
+- 27
+...
+box.snapshot()
+---
+- ok
+...
+s.index.sk:select()
+---
+- - [7, -7]
+  - [6, -6]
+  - [5, -5]
+  - [4, -4]
+...
+s.index.sk:stat().memory.rows
+---
+- 0
+...
+s:drop()
+---
+...
+--
+-- gh-3458: check that rw transactions that started before DDL are
+-- aborted.
+--
+vinyl_cache = box.cfg.vinyl_cache
+---
+...
+box.cfg{vinyl_cache = 0}
+---
+...
+s1 = box.schema.space.create('test1', {engine = 'vinyl'})
+---
+...
+_ = s1:create_index('pk', {page_size = 16})
+---
+...
+s2 = box.schema.space.create('test2', {engine = 'vinyl'})
+---
+...
+_ = s2:create_index('pk')
+---
+...
+pad = string.rep('x', 16)
+---
+...
+for i = 101, 200 do s1:replace{i, i, pad} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function async_replace(space, tuple, timeout)
+    local c = fiber.channel(1)
+    fiber.create(function()
+        box.begin()
+        space:replace(tuple)
+        fiber.sleep(timeout)
+        local status = pcall(box.commit)
+        c:put(status)
+    end)
+    return c
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+c1 = async_replace(s1, {1}, 0.01)
+---
+...
+c2 = async_replace(s2, {1}, 0.01)
+---
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+---
+- ok
+...
+s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}}
+---
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+---
+- ok
+...
+c1:get() -- false (transaction was aborted)
+---
+- false
+...
+c2:get() -- true
+---
+- true
+...
+s1:get(1) == nil
+---
+- true
+...
+s2:get(1) ~= nil
+---
+- true
+...
+s1:format()
+---
+- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'unsigned'}]
+...
+s1:format{}
+---
+...
+c1 = async_replace(s1, {2}, 0.01)
+---
+...
+c2 = async_replace(s2, {2}, 0.01)
+---
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+---
+- ok
+...
+_ = s1:create_index('sk', {parts = {2, 'unsigned'}})
+---
+...
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+---
+- ok
+...
+c1:get() -- false (transaction was aborted)
+---
+- false
+...
+c2:get() -- true
+---
+- true
+...
+s1:get(2) == nil
+---
+- true
+...
+s2:get(2) ~= nil
+---
+- true
+...
+s1.index.pk:count() == s1.index.sk:count()
+---
+- true
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
+box.cfg{vinyl_cache = vinyl_cache}
+---
+...
+-- Transactions that reached WAL must not be aborted.
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+_ = s:create_index('pk')
+---
+...
+errinj.set('ERRINJ_WAL_DELAY', true)
+---
+- ok
+...
+_ = fiber.create(function() s:replace{1} end)
+---
+...
+_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
+---
+...
+fiber.sleep(0)
+---
+...
+s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail
+---
+- error: Tuple field 2 required by space format is missing
+...
+s:select()
+---
+- - [1]
+...
+s:truncate()
+---
+...
+errinj.set('ERRINJ_WAL_DELAY', true)
+---
+- ok
+...
+_ = fiber.create(function() s:replace{1} end)
+---
+...
+_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
+---
+...
+fiber.sleep(0)
+---
+...
+s:create_index('sk', {parts = {2, 'unsigned'}})
+---
+- error: Tuple field 2 required by space format is missing
+...
+s:select()
+---
+- - [1]
+...
+s:drop()
+---
+...
diff --git a/test/vinyl/errinj_ddl.test.lua b/test/vinyl/errinj_ddl.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..0948bc3d34ca929110d00c5cb41f45eb5b982ff2
--- /dev/null
+++ b/test/vinyl/errinj_ddl.test.lua
@@ -0,0 +1,260 @@
+test_run = require('test_run').new()
+fiber = require('fiber')
+errinj = box.error.injection
+
+--
+-- Check that ALTER is abroted if a tuple inserted during space
+-- format change does not conform to the new format.
+--
+format = {}
+format[1] = {name = 'field1', type = 'unsigned'}
+format[2] = {name = 'field2', type = 'string', is_nullable = true}
+s = box.schema.space.create('test', {engine = 'vinyl', format = format})
+_ = s:create_index('pk', {page_size = 16})
+
+pad = string.rep('x', 16)
+for i = 101, 200 do s:replace{i, pad} end
+box.snapshot()
+
+ch = fiber.channel(1)
+test_run:cmd("setopt delimiter ';'")
+_ = fiber.create(function()
+    fiber.sleep(0.01)
+    for i = 1, 100 do
+        s:replace{i, box.NULL}
+    end
+    ch:put(true)
+end);
+test_run:cmd("setopt delimiter ''");
+
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+format[2].is_nullable = false
+s:format(format) -- must fail
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+
+ch:get()
+
+s:count() -- 200
+s:drop()
+
+--
+-- gh-2449: change 'unique' index property from true to false
+-- is done without index rebuild.
+--
+s = box.schema.space.create('test', { engine = 'vinyl' })
+_ = s:create_index('primary')
+_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}})
+s:insert{1, 10}
+box.snapshot()
+errinj.set("ERRINJ_VY_READ_PAGE", true);
+s.index.secondary:alter{unique = false} -- ok
+s.index.secondary.unique
+s.index.secondary:alter{unique = true} -- error
+s.index.secondary.unique
+errinj.set("ERRINJ_VY_READ_PAGE", false);
+s:insert{2, 10}
+s.index.secondary:select(10)
+s:drop()
+
+--
+-- Check that ALTER is aborted if a tuple inserted during index build
+-- doesn't conform to the new format.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('pk', {page_size = 16})
+
+pad = string.rep('x', 16)
+for i = 101, 200 do s:replace{i, i, pad} end
+box.snapshot()
+
+ch = fiber.channel(1)
+test_run:cmd("setopt delimiter ';'")
+_ = fiber.create(function()
+    fiber.sleep(0.01)
+    for i = 1, 100 do
+        s:replace{i}
+    end
+    ch:put(true)
+end);
+test_run:cmd("setopt delimiter ''");
+
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+
+ch:get()
+
+s:count() -- 200
+s:drop()
+
+--
+-- Check that ALTER is aborted if a tuple inserted during index build
+-- violates unique constraint.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('pk', {page_size = 16})
+
+pad = string.rep('x', 16)
+for i = 101, 200 do s:replace{i, i, pad} end
+box.snapshot()
+
+ch = fiber.channel(1)
+test_run:cmd("setopt delimiter ';'")
+_ = fiber.create(function()
+    fiber.sleep(0.01)
+    for i = 1, 100 do
+        s:replace{i, i + 1}
+    end
+    ch:put(true)
+end);
+test_run:cmd("setopt delimiter ''");
+
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+s:create_index('sk', {parts = {2, 'unsigned'}}) -- must fail
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+
+ch:get()
+
+s:count() -- 200
+s:drop()
+
+--
+-- Check that modifications done to the space during the final dump
+-- of a newly built index are recovered properly.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('pk')
+
+for i = 1, 5 do s:replace{i, i} end
+
+errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", true)
+ch = fiber.channel(1)
+_ = fiber.create(function() s:create_index('sk', {parts = {2, 'integer'}}) ch:put(true) end)
+
+fiber.sleep(0.01)
+
+_ = s:delete{1}
+_ = s:replace{2, -2}
+_ = s:delete{2}
+_ = s:replace{3, -3}
+_ = s:replace{3, -2}
+_ = s:replace{3, -1}
+_ = s:delete{3}
+_ = s:upsert({3, 3}, {{'=', 2, 1}})
+_ = s:upsert({3, 3}, {{'=', 2, 2}})
+_ = s:delete{3}
+_ = s:replace{4, -1}
+_ = s:replace{4, -2}
+_ = s:replace{4, -4}
+_ = s:upsert({5, 1}, {{'=', 2, 1}})
+_ = s:upsert({5, 2}, {{'=', 2, -5}})
+_ = s:replace{6, -6}
+_ = s:upsert({7, -7}, {{'=', 2, -7}})
+
+errinj.set("ERRINJ_VY_RUN_WRITE_DELAY", false)
+ch:get()
+
+s.index.sk:select()
+s.index.sk:stat().memory.rows
+
+test_run:cmd('restart server default')
+
+fiber = require('fiber')
+errinj = box.error.injection
+
+s = box.space.test
+
+s.index.sk:select()
+s.index.sk:stat().memory.rows
+
+box.snapshot()
+
+s.index.sk:select()
+s.index.sk:stat().memory.rows
+
+s:drop()
+
+--
+-- gh-3458: check that rw transactions that started before DDL are
+-- aborted.
+--
+vinyl_cache = box.cfg.vinyl_cache
+box.cfg{vinyl_cache = 0}
+
+s1 = box.schema.space.create('test1', {engine = 'vinyl'})
+_ = s1:create_index('pk', {page_size = 16})
+s2 = box.schema.space.create('test2', {engine = 'vinyl'})
+_ = s2:create_index('pk')
+
+pad = string.rep('x', 16)
+for i = 101, 200 do s1:replace{i, i, pad} end
+box.snapshot()
+
+test_run:cmd("setopt delimiter ';'")
+function async_replace(space, tuple, timeout)
+    local c = fiber.channel(1)
+    fiber.create(function()
+        box.begin()
+        space:replace(tuple)
+        fiber.sleep(timeout)
+        local status = pcall(box.commit)
+        c:put(status)
+    end)
+    return c
+end;
+test_run:cmd("setopt delimiter ''");
+
+c1 = async_replace(s1, {1}, 0.01)
+c2 = async_replace(s2, {1}, 0.01)
+
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+s1:format{{'key', 'unsigned'}, {'value', 'unsigned'}}
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+
+c1:get() -- false (transaction was aborted)
+c2:get() -- true
+
+s1:get(1) == nil
+s2:get(1) ~= nil
+s1:format()
+s1:format{}
+
+c1 = async_replace(s1, {2}, 0.01)
+c2 = async_replace(s2, {2}, 0.01)
+
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001)
+_ = s1:create_index('sk', {parts = {2, 'unsigned'}})
+errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0)
+
+c1:get() -- false (transaction was aborted)
+c2:get() -- true
+
+s1:get(2) == nil
+s2:get(2) ~= nil
+s1.index.pk:count() == s1.index.sk:count()
+
+s1:drop()
+s2:drop()
+box.cfg{vinyl_cache = vinyl_cache}
+
+-- Transactions that reached WAL must not be aborted.
+s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('pk')
+
+errinj.set('ERRINJ_WAL_DELAY', true)
+_ = fiber.create(function() s:replace{1} end)
+_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
+
+fiber.sleep(0)
+s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail
+s:select()
+s:truncate()
+
+errinj.set('ERRINJ_WAL_DELAY', true)
+_ = fiber.create(function() s:replace{1} end)
+_ = fiber.create(function() fiber.sleep(0.01) errinj.set('ERRINJ_WAL_DELAY', false) end)
+
+fiber.sleep(0)
+s:create_index('sk', {parts = {2, 'unsigned'}})
+s:select()
+s:drop()
diff --git a/test/vinyl/errinj_stat.result b/test/vinyl/errinj_stat.result
new file mode 100644
index 0000000000000000000000000000000000000000..fac56cee25fed3d9f7529b7d6dd786ae57aa191e
--- /dev/null
+++ b/test/vinyl/errinj_stat.result
@@ -0,0 +1,152 @@
+test_run = require('test_run').new()
+---
+...
+-- Since we store LSNs in data files, the data size may differ
+-- from run to run. Deploy a new server to make sure it will be
+-- the same so that we can check it.
+test_run:cmd('create server test with script = "vinyl/stat.lua"')
+---
+- true
+...
+test_run:cmd('start server test')
+---
+- true
+...
+test_run:cmd('switch test')
+---
+- true
+...
+-- Compressed data size depends on the zstd version so let's
+-- filter it out.
+test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: <bytes_compressed>'")
+---
+- true
+...
+fiber = require('fiber')
+---
+...
+errinj = box.error.injection
+---
+...
+--
+-- Check disk.compact.queue stat.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+i = s:create_index('pk', {run_count_per_level = 2})
+---
+...
+function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end
+---
+...
+dump()
+---
+...
+i:stat().disk.compact.queue -- none
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 0
+  rows: 0
+  bytes: 0
+...
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+---
+- true
+...
+errinj.set('ERRINJ_VY_COMPACTION_DELAY', true)
+---
+- ok
+...
+dump()
+---
+...
+dump()
+---
+...
+i:stat().disk.compact.queue -- 30 statements
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 3
+  rows: 30
+  bytes: 411
+...
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+---
+- true
+...
+dump()
+---
+...
+i:stat().disk.compact.queue -- 40 statements
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 4
+  rows: 40
+  bytes: 548
+...
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+---
+- true
+...
+dump()
+---
+...
+i:stat().disk.compact.queue -- 50 statements
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 5
+  rows: 50
+  bytes: 685
+...
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+---
+- true
+...
+box.stat.reset() -- doesn't affect queue size
+---
+...
+i:stat().disk.compact.queue -- 50 statements
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 5
+  rows: 50
+  bytes: 685
+...
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+---
+- true
+...
+errinj.set('ERRINJ_VY_COMPACTION_DELAY', false)
+---
+- ok
+...
+while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end
+---
+...
+i:stat().disk.compact.queue -- none
+---
+- bytes_compressed: <bytes_compressed>
+  pages: 0
+  rows: 0
+  bytes: 0
+...
+s:drop()
+---
+...
+test_run:cmd("clear filter")
+---
+- true
+...
+test_run:cmd('switch default')
+---
+- true
+...
+test_run:cmd('stop server test')
+---
+- true
+...
+test_run:cmd('cleanup server test')
+---
+- true
+...
diff --git a/test/vinyl/errinj_stat.test.lua b/test/vinyl/errinj_stat.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1e0e63aef9dcdec6d96209745072290ebe1c8018
--- /dev/null
+++ b/test/vinyl/errinj_stat.test.lua
@@ -0,0 +1,48 @@
+test_run = require('test_run').new()
+
+-- Since we store LSNs in data files, the data size may differ
+-- from run to run. Deploy a new server to make sure it will be
+-- the same so that we can check it.
+test_run:cmd('create server test with script = "vinyl/stat.lua"')
+test_run:cmd('start server test')
+test_run:cmd('switch test')
+
+-- Compressed data size depends on the zstd version so let's
+-- filter it out.
+test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: <bytes_compressed>'")
+
+fiber = require('fiber')
+errinj = box.error.injection
+
+--
+-- Check disk.compact.queue stat.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+i = s:create_index('pk', {run_count_per_level = 2})
+function dump() for i = 1, 10 do s:replace{i} end box.snapshot() end
+dump()
+i:stat().disk.compact.queue -- none
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+errinj.set('ERRINJ_VY_COMPACTION_DELAY', true)
+dump()
+dump()
+i:stat().disk.compact.queue -- 30 statements
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+dump()
+i:stat().disk.compact.queue -- 40 statements
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+dump()
+i:stat().disk.compact.queue -- 50 statements
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+box.stat.reset() -- doesn't affect queue size
+i:stat().disk.compact.queue -- 50 statements
+i:stat().disk.compact.queue.bytes == box.stat.vinyl().disk.compact.queue
+errinj.set('ERRINJ_VY_COMPACTION_DELAY', false)
+while i:stat().disk.compact.count < 2 do fiber.sleep(0.01) end
+i:stat().disk.compact.queue -- none
+s:drop()
+
+test_run:cmd("clear filter")
+test_run:cmd('switch default')
+test_run:cmd('stop server test')
+test_run:cmd('cleanup server test')
diff --git a/test/vinyl/errinj_tx.result b/test/vinyl/errinj_tx.result
new file mode 100644
index 0000000000000000000000000000000000000000..a7583b2e22ac3beda60ae0b8f152873fbd52d21e
--- /dev/null
+++ b/test/vinyl/errinj_tx.result
@@ -0,0 +1,435 @@
+test_run = require('test_run').new()
+---
+...
+fiber = require('fiber')
+---
+...
+txn_proxy = require('txn_proxy')
+---
+...
+create_iterator = require('utils').create_iterator
+---
+...
+errinj = box.error.injection
+---
+...
+--
+-- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE
+--
+s = box.schema.space.create('test', {engine='vinyl'})
+---
+...
+_ = s:create_index('pk')
+---
+...
+function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end
+---
+...
+errinj.set("ERRINJ_WAL_WRITE", true)
+---
+- ok
+...
+f()
+---
+- error: Failed to write to disk
+...
+s:select{}
+---
+- []
+...
+errinj.set("ERRINJ_WAL_WRITE", false)
+---
+- ok
+...
+f()
+---
+...
+s:select{}
+---
+- - [1, 'hi']
+  - [2, 'bye']
+...
+s:drop()
+---
+...
+--https://github.com/tarantool/tarantool/issues/1842
+--test error injection
+s = box.schema.space.create('test', {engine='vinyl'})
+---
+...
+_ = s:create_index('pk')
+---
+...
+s:replace{0, 0}
+---
+- [0, 0]
+...
+s:replace{1, 0}
+---
+- [1, 0]
+...
+s:replace{2, 0}
+---
+- [2, 0]
+...
+errinj.set("ERRINJ_WAL_WRITE", true)
+---
+- ok
+...
+s:replace{3, 0}
+---
+- error: Failed to write to disk
+...
+s:replace{4, 0}
+---
+- error: Failed to write to disk
+...
+s:replace{5, 0}
+---
+- error: Failed to write to disk
+...
+s:replace{6, 0}
+---
+- error: Failed to write to disk
+...
+errinj.set("ERRINJ_WAL_WRITE", false)
+---
+- ok
+...
+s:replace{7, 0}
+---
+- [7, 0]
+...
+s:replace{8, 0}
+---
+- [8, 0]
+...
+s:select{}
+---
+- - [0, 0]
+  - [1, 0]
+  - [2, 0]
+  - [7, 0]
+  - [8, 0]
+...
+s:drop()
+---
+...
+--iterator test
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+fiber_status = 0
+
+function fiber_func()
+    box.begin()
+    s:replace{5, 5}
+    fiber_status = 1
+    local res = {pcall(box.commit) }
+    fiber_status = 2
+    return unpack(res)
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+s = box.schema.space.create('test', {engine='vinyl'})
+---
+...
+_ = s:create_index('pk')
+---
+...
+_ = s:replace{0, 0}
+---
+...
+_ = s:replace{10, 0}
+---
+...
+_ = s:replace{20, 0}
+---
+...
+test_run:cmd("setopt delimiter ';'");
+---
+- true
+...
+faced_trash = false
+for i = 1,100 do
+    errinj.set("ERRINJ_WAL_WRITE", true)
+    local f = fiber.create(fiber_func)
+    local itr = create_iterator(s, {0}, {iterator='GE'})
+    local first = itr.next()
+    local second = itr.next()
+    if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end
+    while fiber_status <= 1 do fiber.sleep(0.001) end
+    local _,next = pcall(itr.next)
+    _,next = pcall(itr.next)
+    _,next = pcall(itr.next)
+    errinj.set("ERRINJ_WAL_WRITE", false)
+    s:delete{5}
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+faced_trash
+---
+- false
+...
+s:drop()
+---
+...
+-- TX in prepared but not committed state
+s = box.schema.space.create('test', {engine='vinyl'})
+---
+...
+_ = s:create_index('pk')
+---
+...
+s:replace{1, "original"}
+---
+- [1, 'original']
+...
+s:replace{2, "original"}
+---
+- [2, 'original']
+...
+s:replace{3, "original"}
+---
+- [3, 'original']
+...
+c0 = txn_proxy.new()
+---
+...
+c0:begin()
+---
+- 
+...
+c1 = txn_proxy.new()
+---
+...
+c1:begin()
+---
+- 
+...
+c2 = txn_proxy.new()
+---
+...
+c2:begin()
+---
+- 
+...
+c3 = txn_proxy.new()
+---
+...
+c3:begin()
+---
+- 
+...
+--
+-- Prepared transactions
+--
+-- Pause WAL writer to cause all further calls to box.commit() to move
+-- transactions into prepared, but not committed yet state.
+errinj.set("ERRINJ_WAL_DELAY", true)
+---
+- ok
+...
+lsn = box.info.lsn
+---
+...
+c0('s:replace{1, "c0"}')
+---
+- - [1, 'c0']
+...
+c0('s:replace{2, "c0"}')
+---
+- - [2, 'c0']
+...
+c0('s:replace{3, "c0"}')
+---
+- - [3, 'c0']
+...
+_ = fiber.create(c0.commit, c0)
+---
+...
+box.info.lsn == lsn
+---
+- true
+...
+c1('s:replace{1, "c1"}')
+---
+- - [1, 'c1']
+...
+c1('s:replace{2, "c1"}')
+---
+- - [2, 'c1']
+...
+_ = fiber.create(c1.commit, c1)
+---
+...
+box.info.lsn == lsn
+---
+- true
+...
+c3('s:select{1}') -- c1 is visible
+---
+- - [[1, 'c1']]
+...
+c2('s:replace{1, "c2"}')
+---
+- - [1, 'c2']
+...
+c2('s:replace{3, "c2"}')
+---
+- - [3, 'c2']
+...
+_ = fiber.create(c2.commit, c2)
+---
+...
+box.info.lsn == lsn
+---
+- true
+...
+c3('s:select{1}') -- c1 is visible, c2 is not
+---
+- - [[1, 'c1']]
+...
+c3('s:select{2}') -- c1 is visible
+---
+- - [[2, 'c1']]
+...
+c3('s:select{3}') -- c2 is not visible
+---
+- - [[3, 'c0']]
+...
+-- Resume WAL writer and wait until all transactions will been committed
+errinj.set("ERRINJ_WAL_DELAY", false)
+---
+- ok
+...
+REQ_COUNT = 7
+---
+...
+while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end
+---
+...
+box.info.lsn == lsn + REQ_COUNT
+---
+- true
+...
+c3('s:select{1}') -- c1 is visible, c2 is not
+---
+- - [[1, 'c1']]
+...
+c3('s:select{2}') -- c1 is visible
+---
+- - [[2, 'c1']]
+...
+c3('s:select{3}') -- c2 is not visible
+---
+- - [[3, 'c0']]
+...
+c3:commit()
+---
+- 
+...
+s:drop()
+---
+...
+--
+-- Test mem restoration on a prepared and not commited statement
+-- after moving iterator into read view.
+--
+space = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+pk = space:create_index('pk')
+---
+...
+space:replace{1}
+---
+- [1]
+...
+space:replace{2}
+---
+- [2]
+...
+space:replace{3}
+---
+- [3]
+...
+last_read = nil
+---
+...
+errinj.set("ERRINJ_WAL_DELAY", true)
+---
+- ok
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+-- block until wal_delay = false
+-- send iterator to read view
+-- flush mem and update index version to trigger iterator restore
+function fill_space()
+    box.begin()
+    space:replace{1}
+    space:replace{2}
+    space:replace{3}
+    box.commit()
+    space:replace{1, 1}
+    box.snapshot()
+end;
+---
+...
+function iterate_in_read_view()
+    local i = create_iterator(space)
+    last_read = i.next()
+    fiber.sleep(100000)
+    last_read = i.next()
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+f1 = fiber.create(fill_space)
+---
+...
+-- Prepared transaction is blocked due to wal_delay.
+-- Start iterator with vlsn = INT64_MAX
+f2 = fiber.create(iterate_in_read_view)
+---
+...
+last_read
+---
+- [1]
+...
+-- Finish prepared transaction and send to read view the iterator.
+errinj.set("ERRINJ_WAL_DELAY", false)
+---
+- ok
+...
+while f1:status() ~= 'dead' do fiber.sleep(0.01) end
+---
+...
+f2:wakeup()
+---
+...
+while f2:status() ~= 'dead' do fiber.sleep(0.01) end
+---
+...
+last_read
+---
+- [2]
+...
+space:drop()
+---
+...
diff --git a/test/vinyl/errinj_tx.test.lua b/test/vinyl/errinj_tx.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..cf83b32625d14feab529a6186cc6cf624c7dd20d
--- /dev/null
+++ b/test/vinyl/errinj_tx.test.lua
@@ -0,0 +1,194 @@
+test_run = require('test_run').new()
+fiber = require('fiber')
+txn_proxy = require('txn_proxy')
+create_iterator = require('utils').create_iterator
+errinj = box.error.injection
+
+--
+-- gh-1681: vinyl: crash in vy_rollback on ER_WAL_WRITE
+--
+s = box.schema.space.create('test', {engine='vinyl'})
+_ = s:create_index('pk')
+function f() box.begin() s:insert{1, 'hi'} s:insert{2, 'bye'} box.commit() end
+errinj.set("ERRINJ_WAL_WRITE", true)
+f()
+s:select{}
+errinj.set("ERRINJ_WAL_WRITE", false)
+f()
+s:select{}
+s:drop()
+
+--https://github.com/tarantool/tarantool/issues/1842
+--test error injection
+s = box.schema.space.create('test', {engine='vinyl'})
+_ = s:create_index('pk')
+s:replace{0, 0}
+
+s:replace{1, 0}
+s:replace{2, 0}
+errinj.set("ERRINJ_WAL_WRITE", true)
+s:replace{3, 0}
+s:replace{4, 0}
+s:replace{5, 0}
+s:replace{6, 0}
+errinj.set("ERRINJ_WAL_WRITE", false)
+s:replace{7, 0}
+s:replace{8, 0}
+s:select{}
+
+s:drop()
+
+--iterator test
+test_run:cmd("setopt delimiter ';'")
+
+fiber_status = 0
+
+function fiber_func()
+    box.begin()
+    s:replace{5, 5}
+    fiber_status = 1
+    local res = {pcall(box.commit) }
+    fiber_status = 2
+    return unpack(res)
+end;
+
+test_run:cmd("setopt delimiter ''");
+
+s = box.schema.space.create('test', {engine='vinyl'})
+_ = s:create_index('pk')
+
+_ = s:replace{0, 0}
+_ = s:replace{10, 0}
+_ = s:replace{20, 0}
+
+test_run:cmd("setopt delimiter ';'");
+
+faced_trash = false
+for i = 1,100 do
+    errinj.set("ERRINJ_WAL_WRITE", true)
+    local f = fiber.create(fiber_func)
+    local itr = create_iterator(s, {0}, {iterator='GE'})
+    local first = itr.next()
+    local second = itr.next()
+    if (second[1] ~= 5 and second[1] ~= 10) then faced_trash = true end
+    while fiber_status <= 1 do fiber.sleep(0.001) end
+    local _,next = pcall(itr.next)
+    _,next = pcall(itr.next)
+    _,next = pcall(itr.next)
+    errinj.set("ERRINJ_WAL_WRITE", false)
+    s:delete{5}
+end;
+
+test_run:cmd("setopt delimiter ''");
+
+faced_trash
+
+s:drop()
+
+-- TX in prepared but not committed state
+s = box.schema.space.create('test', {engine='vinyl'})
+_ = s:create_index('pk')
+
+s:replace{1, "original"}
+s:replace{2, "original"}
+s:replace{3, "original"}
+
+c0 = txn_proxy.new()
+c0:begin()
+c1 = txn_proxy.new()
+c1:begin()
+c2 = txn_proxy.new()
+c2:begin()
+c3 = txn_proxy.new()
+c3:begin()
+
+--
+-- Prepared transactions
+--
+
+-- Pause WAL writer to cause all further calls to box.commit() to move
+-- transactions into prepared, but not committed yet state.
+errinj.set("ERRINJ_WAL_DELAY", true)
+lsn = box.info.lsn
+c0('s:replace{1, "c0"}')
+c0('s:replace{2, "c0"}')
+c0('s:replace{3, "c0"}')
+_ = fiber.create(c0.commit, c0)
+box.info.lsn == lsn
+c1('s:replace{1, "c1"}')
+c1('s:replace{2, "c1"}')
+_ = fiber.create(c1.commit, c1)
+box.info.lsn == lsn
+c3('s:select{1}') -- c1 is visible
+c2('s:replace{1, "c2"}')
+c2('s:replace{3, "c2"}')
+_ = fiber.create(c2.commit, c2)
+box.info.lsn == lsn
+c3('s:select{1}') -- c1 is visible, c2 is not
+c3('s:select{2}') -- c1 is visible
+c3('s:select{3}') -- c2 is not visible
+
+-- Resume WAL writer and wait until all transactions will been committed
+errinj.set("ERRINJ_WAL_DELAY", false)
+REQ_COUNT = 7
+while box.info.lsn - lsn < REQ_COUNT do fiber.sleep(0.01) end
+box.info.lsn == lsn + REQ_COUNT
+
+c3('s:select{1}') -- c1 is visible, c2 is not
+c3('s:select{2}') -- c1 is visible
+c3('s:select{3}') -- c2 is not visible
+c3:commit()
+
+s:drop()
+
+--
+-- Test mem restoration on a prepared and not commited statement
+-- after moving iterator into read view.
+--
+space = box.schema.space.create('test', {engine = 'vinyl'})
+pk = space:create_index('pk')
+space:replace{1}
+space:replace{2}
+space:replace{3}
+
+last_read = nil
+
+errinj.set("ERRINJ_WAL_DELAY", true)
+
+test_run:cmd("setopt delimiter ';'")
+
+function fill_space()
+    box.begin()
+    space:replace{1}
+    space:replace{2}
+    space:replace{3}
+-- block until wal_delay = false
+    box.commit()
+-- send iterator to read view
+    space:replace{1, 1}
+-- flush mem and update index version to trigger iterator restore
+    box.snapshot()
+end;
+
+function iterate_in_read_view()
+    local i = create_iterator(space)
+    last_read = i.next()
+    fiber.sleep(100000)
+    last_read = i.next()
+end;
+
+test_run:cmd("setopt delimiter ''");
+
+f1 = fiber.create(fill_space)
+-- Prepared transaction is blocked due to wal_delay.
+-- Start iterator with vlsn = INT64_MAX
+f2 = fiber.create(iterate_in_read_view)
+last_read
+-- Finish prepared transaction and send to read view the iterator.
+errinj.set("ERRINJ_WAL_DELAY", false)
+while f1:status() ~= 'dead' do fiber.sleep(0.01) end
+f2:wakeup()
+while f2:status() ~= 'dead' do fiber.sleep(0.01) end
+last_read
+
+space:drop()
diff --git a/test/vinyl/errinj_vylog.result b/test/vinyl/errinj_vylog.result
index 9ced03df131daaf8c041f6de9ddbe64dc3eb708e..0e3b79c4a00643c848656f4e70e2ccbadf7b3dac 100644
--- a/test/vinyl/errinj_vylog.result
+++ b/test/vinyl/errinj_vylog.result
@@ -5,6 +5,41 @@ fiber = require('fiber')
 ---
 ...
 --
+-- Check that DDL operations are logged in vylog only after successful
+-- WAL write.
+--
+-- If we logged an index creation in the metadata log before WAL write,
+-- WAL failure would result in leaving the index record in vylog forever.
+-- Since we use LSN to identify indexes in vylog, retrying index creation
+-- would then lead to a duplicate index id in vylog and hence inability
+-- to make a snapshot or recover.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+box.error.injection.set('ERRINJ_WAL_IO', true)
+---
+- ok
+...
+_ = s:create_index('pk')
+---
+- error: Failed to write to disk
+...
+box.error.injection.set('ERRINJ_WAL_IO', false)
+---
+- ok
+...
+_ = s:create_index('pk')
+---
+...
+box.snapshot()
+---
+- ok
+...
+s:drop()
+---
+...
+--
 -- Check that an error to commit a new run to vylog does not
 -- break vinyl permanently.
 --
diff --git a/test/vinyl/errinj_vylog.test.lua b/test/vinyl/errinj_vylog.test.lua
index c1fd517d3307a38ecc0775747a056e2fb398cab9..ce9e12e5d9a169b98286f0d424fe0f662cea11fd 100644
--- a/test/vinyl/errinj_vylog.test.lua
+++ b/test/vinyl/errinj_vylog.test.lua
@@ -1,6 +1,24 @@
 test_run = require('test_run').new()
 fiber = require('fiber')
 
+--
+-- Check that DDL operations are logged in vylog only after successful
+-- WAL write.
+--
+-- If we logged an index creation in the metadata log before WAL write,
+-- WAL failure would result in leaving the index record in vylog forever.
+-- Since we use LSN to identify indexes in vylog, retrying index creation
+-- would then lead to a duplicate index id in vylog and hence inability
+-- to make a snapshot or recover.
+--
+s = box.schema.space.create('test', {engine = 'vinyl'})
+box.error.injection.set('ERRINJ_WAL_IO', true)
+_ = s:create_index('pk')
+box.error.injection.set('ERRINJ_WAL_IO', false)
+_ = s:create_index('pk')
+box.snapshot()
+s:drop()
+
 --
 -- Check that an error to commit a new run to vylog does not
 -- break vinyl permanently.
diff --git a/test/vinyl/suite.ini b/test/vinyl/suite.ini
index ace53e7f1f5fc99a3136117bc41820c71ec2115e..d2a194d85997c7a071e5d4f050be1cdc8504b4ec 100644
--- a/test/vinyl/suite.ini
+++ b/test/vinyl/suite.ini
@@ -2,7 +2,7 @@
 core = tarantool
 description = vinyl integration tests
 script = vinyl.lua
-release_disabled = errinj.test.lua errinj_gc.test.lua errinj_vylog.test.lua partial_dump.test.lua quota_timeout.test.lua recovery_quota.test.lua replica_rejoin.test.lua
+release_disabled = errinj.test.lua errinj_ddl.test.lua errinj_gc.test.lua errinj_stat.test.lua errinj_tx.test.lua errinj_vylog.test.lua partial_dump.test.lua quota_timeout.test.lua recovery_quota.test.lua replica_rejoin.test.lua
 config = suite.cfg
 lua_libs = suite.lua stress.lua large.lua txn_proxy.lua ../box/lua/utils.lua
 use_unix_sockets = True