[PATCH 02/12] test: split vinyl/errinj

Vladimir Davydov vdavydov.dev at gmail.com
Tue Jan 15 17:17:11 MSK 2019


This test is huge and takes long to complete. Let's move ddl, tx, and
stat related stuff to separate files.
---
 test/vinyl/errinj.result         | 1158 --------------------------------------
 test/vinyl/errinj.test.lua       |  494 ----------------
 test/vinyl/errinj_ddl.result     |  595 ++++++++++++++++++++
 test/vinyl/errinj_ddl.test.lua   |  260 +++++++++
 test/vinyl/errinj_stat.result    |  152 +++++
 test/vinyl/errinj_stat.test.lua  |   48 ++
 test/vinyl/errinj_tx.result      |  435 ++++++++++++++
 test/vinyl/errinj_tx.test.lua    |  194 +++++++
 test/vinyl/errinj_vylog.result   |   35 ++
 test/vinyl/errinj_vylog.test.lua |   18 +
 test/vinyl/suite.ini             |    2 +-
 11 files changed, 1738 insertions(+), 1653 deletions(-)
 create mode 100644 test/vinyl/errinj_ddl.result
 create mode 100644 test/vinyl/errinj_ddl.test.lua
 create mode 100644 test/vinyl/errinj_stat.result
 create mode 100644 test/vinyl/errinj_stat.test.lua
 create mode 100644 test/vinyl/errinj_tx.result
 create mode 100644 test/vinyl/errinj_tx.test.lua

diff --git a/test/vinyl/errinj.result b/test/vinyl/errinj.result
index 23ab845b..a16475f5 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,399 +435,6 @@ 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')
----
-...
-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()
----
-...
-create_iterator = require('utils').create_iterator
----
-...
---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')
----
-...
-fiber = require('fiber')
----
-...
-_ = 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')
----
-...
-fiber = require('fiber')
----
-...
-txn_proxy = require('txn_proxy')
----
-...
-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()
----
-...
 --
 -- Space drop in the middle of dump.
 --
@@ -976,38 +544,6 @@ 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'})
 ---
 ...
@@ -1392,81 +928,6 @@ 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.
 --
@@ -1533,317 +994,6 @@ 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()
----
-...
---
 -- Check that tarantool doesn't hang or crash if error
 -- occurs while writing a deferred DELETE to WAL.
 --
@@ -1926,314 +1076,6 @@ 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()
----
-...
---
--- Check disk.compact.queue stat.
---
-test_run:cmd("push filter 'bytes_compressed: .*' to 'bytes_compressed: <bytes_compressed>'")
----
-- true
-...
-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: 471
-...
-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: 628
-...
-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: 785
-...
-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: 785
-...
-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
-...
---
 -- 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 59e9f81c..d4590fb4 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'}})
 
@@ -541,41 +333,6 @@ 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.
 --
@@ -604,140 +361,6 @@ 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.
 --
@@ -768,123 +391,6 @@ 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 00000000..4f5d59cd
--- /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 00000000..0948bc3d
--- /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 00000000..fac56cee
--- /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 00000000..1e0e63ae
--- /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 00000000..a7583b2e
--- /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 00000000..cf83b326
--- /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 9ced03df..0e3b79c4 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 c1fd517d..ce9e12e5 100644
--- a/test/vinyl/errinj_vylog.test.lua
+++ b/test/vinyl/errinj_vylog.test.lua
@@ -2,6 +2,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 ace53e7f..d2a194d8 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
-- 
2.11.0




More information about the Tarantool-patches mailing list