neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

test_recover.vim (14419B)


      1 " Test :recover
      2 
      3 source check.vim
      4 
      5 func Test_recover_root_dir()
      6  " This used to access invalid memory.
      7  split Xtest
      8  set dir=/
      9  call assert_fails('recover', 'E305:')
     10  close!
     11 
     12  if has('win32')
     13    " can write in / directory on MS-Windows
     14    let &directory = 'F:\\'
     15  elseif filewritable('/') == 2
     16    set dir=/notexist/
     17  endif
     18  call assert_fails('split Xtest', 'E303:')
     19 
     20  " No error with empty 'directory' setting.
     21  set directory=
     22  split XtestOK
     23  close!
     24 
     25  set dir&
     26 endfunc
     27 
     28 " Make a copy of the current swap file to "Xswap".
     29 " Return the name of the swap file.
     30 func CopySwapfile()
     31  preserve
     32  " get the name of the swap file
     33  let swname = split(execute("swapname"))[0]
     34  let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '')
     35  " make a copy of the swap file in Xswap
     36  set binary
     37  exe 'sp ' . swname
     38  w! Xswap
     39  set nobinary
     40  return swname
     41 endfunc
     42 
     43 " Inserts 10000 lines with text to fill the swap file with two levels of pointer
     44 " blocks.  Then recovers from the swap file and checks all text is restored.
     45 "
     46 " We need about 10000 lines of 100 characters to get two levels of pointer
     47 " blocks.
     48 func Test_swap_file()
     49  set directory=.
     50  set fileformat=unix undolevels=-1
     51  edit! Xtest
     52  let text = "\tabcdefghijklmnoparstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnoparstuvwxyz0123456789"
     53  let i = 1
     54  let linecount = 10000
     55  while i <= linecount
     56    call append(i - 1, i . text)
     57    let i += 1
     58  endwhile
     59  $delete
     60 
     61  let swname = CopySwapfile()
     62 
     63  new
     64  only!
     65  bwipe! Xtest
     66  call rename('Xswap', swname)
     67  recover Xtest
     68  call delete(swname)
     69  let linedollar = line('$')
     70  call assert_equal(linecount, linedollar)
     71  if linedollar < linecount
     72    let linecount = linedollar
     73  endif
     74  let i = 1
     75  while i <= linecount
     76    call assert_equal(i . text, getline(i))
     77    let i += 1
     78  endwhile
     79 
     80  set undolevels&
     81  enew! | only
     82 endfunc
     83 
     84 func Test_nocatch_process_still_running()
     85  let g:skipped_reason = 'test_override() is N/A'
     86  return
     87  " sysinfo.uptime probably only works on Linux
     88  if !has('linux')
     89    let g:skipped_reason = 'only works on Linux'
     90    return
     91  endif
     92  " the GUI dialog can't be handled
     93  if has('gui_running')
     94    let g:skipped_reason = 'only works in the terminal'
     95    return
     96  endif
     97 
     98  " don't intercept existing swap file here
     99  au! SwapExists
    100 
    101  " Edit a file and grab its swapfile.
    102  edit Xswaptest
    103  call setline(1, ['a', 'b', 'c'])
    104  let swname = CopySwapfile()
    105 
    106  " Forget we edited this file
    107  new
    108  only!
    109  bwipe! Xswaptest
    110 
    111  call rename('Xswap', swname)
    112  call feedkeys('e', 'tL')
    113  redir => editOutput
    114  edit Xswaptest
    115  redir END
    116  call assert_match('E325: ATTENTION', editOutput)
    117  call assert_match('file name: .*Xswaptest', editOutput)
    118  call assert_match('process ID: \d* (STILL RUNNING)', editOutput)
    119 
    120  " Forget we edited this file
    121  new
    122  only!
    123  bwipe! Xswaptest
    124 
    125  " pretend we rebooted
    126  call test_override("uptime", 0)
    127  sleep 1
    128 
    129  call feedkeys('e', 'tL')
    130  redir => editOutput
    131  edit Xswaptest
    132  redir END
    133  call assert_match('E325: ATTENTION', editOutput)
    134  call assert_notmatch('(STILL RUNNING)', editOutput)
    135 
    136  call test_override("ALL", 0)
    137  call delete(swname)
    138 endfunc
    139 
    140 " Test for :recover with multiple swap files
    141 func Test_recover_multiple_swap_files()
    142  CheckUnix
    143  new Xfile1
    144  call setline(1, ['a', 'b', 'c'])
    145  preserve
    146  let b = readblob(swapname(''))
    147  call writefile(b, '.Xfile1.swm', 'D')
    148  call writefile(b, '.Xfile1.swn', 'D')
    149  call writefile(b, '.Xfile1.swo', 'D')
    150  %bw!
    151  call feedkeys(":recover Xfile1\<CR>3\<CR>q", 'xt')
    152  call assert_equal(['a', 'b', 'c'], getline(1, '$'))
    153  " try using out-of-range number to select a swap file
    154  bw!
    155  call feedkeys(":recover Xfile1\<CR>4\<CR>q", 'xt')
    156  call assert_equal('Xfile1', @%)
    157  call assert_equal([''], getline(1, '$'))
    158  bw!
    159  call feedkeys(":recover Xfile1\<CR>0\<CR>q", 'xt')
    160  call assert_equal('Xfile1', @%)
    161  call assert_equal([''], getline(1, '$'))
    162  bw!
    163 endfunc
    164 
    165 " Test for :recover using an empty swap file
    166 func Test_recover_empty_swap_file()
    167  CheckUnix
    168  call writefile([], '.Xfile1.swp', 'D')
    169  set dir=.
    170  let msg = execute('recover Xfile1')
    171  call assert_match('Unable to read block 0 from .Xfile1.swp', msg)
    172  call assert_equal('Xfile1', @%)
    173  bw!
    174 
    175  " make sure there are no old swap files laying around
    176  for f in glob('.sw?', 0, 1)
    177    call delete(f)
    178  endfor
    179 
    180  " :recover from an empty buffer
    181  call assert_fails('recover', 'E305:')
    182  set dir&vim
    183 endfunc
    184 
    185 " Test for :recover using a corrupted swap file
    186 " Refer to the comments in the memline.c file for the swap file headers
    187 " definition.
    188 func Test_recover_corrupted_swap_file()
    189  CheckUnix
    190 
    191  " recover using a partial swap file
    192  call writefile(0z1234, '.Xfile1.swp')
    193  call assert_fails('recover Xfile1', 'E295:')
    194  bw!
    195 
    196  " recover using invalid content in the swap file
    197  call writefile([repeat('1', 2*1024)], '.Xfile1.swp')
    198  call assert_fails('recover Xfile1', 'E307:')
    199  call delete('.Xfile1.swp')
    200 
    201  " :recover using a swap file with a corrupted header
    202  edit Xfile1
    203  preserve
    204  let sn = swapname('')
    205  let b = readblob(sn)
    206  let save_b = copy(b)
    207  bw!
    208 
    209  " Not all fields are written in a system-independent manner.  Detect whether
    210  " the test is running on a little or big-endian system, so the correct
    211  " corruption values can be set.
    212  " The B0_MAGIC_LONG field may be 32-bit or 64-bit, depending on the system,
    213  " even though the value stored is only 32-bits.  Therefore, need to check
    214  " both the high and low 32-bits to compute these values.
    215  let little_endian = (b[1008:1011] == 0z33323130) || (b[1012:1015] == 0z33323130)
    216  let system_64bit = little_endian ? (b[1012:1015] == 0z00000000) : (b[1008:1011] == 0z00000000)
    217 
    218  " clear the B0_MAGIC_LONG field
    219  if system_64bit
    220    let b[1008:1015] = 0z00000000.00000000
    221  else
    222    let b[1008:1011] = 0z00000000
    223  endif
    224  call writefile(b, sn)
    225  let msg = execute('recover Xfile1')
    226  call assert_match('the file has been damaged', msg)
    227  call assert_equal('Xfile1', @%)
    228  call assert_equal([''], getline(1, '$'))
    229  bw!
    230 
    231  " reduce the page size
    232  let b = copy(save_b)
    233  let b[12:15] = 0z00010000
    234  call writefile(b, sn)
    235  let msg = execute('recover Xfile1')
    236  call assert_match('page size is smaller than minimum value', msg)
    237  call assert_equal('Xfile1', @%)
    238  call assert_equal([''], getline(1, '$'))
    239  bw!
    240 
    241  " clear the pointer ID
    242  let b = copy(save_b)
    243  let b[4096:4097] = 0z0000
    244  call writefile(b, sn)
    245  call assert_fails('recover Xfile1', 'E310:')
    246  call assert_equal('Xfile1', @%)
    247  call assert_equal([''], getline(1, '$'))
    248  bw!
    249 
    250  " set the number of pointers in a pointer block to zero
    251  let b = copy(save_b)
    252  let b[4098:4099] = 0z0000
    253  call writefile(b, sn)
    254  call assert_fails('recover Xfile1', 'E312:')
    255  call assert_equal('Xfile1', @%)
    256  call assert_equal(['???EMPTY BLOCK'], getline(1, '$'))
    257  bw!
    258 
    259  " set the number of pointers in a pointer block to a large value
    260  let b = copy(save_b)
    261  let b[4098:4099] = 0zFFFF
    262  call writefile(b, sn)
    263  call assert_fails('recover Xfile1', 'E1364:')
    264  call assert_equal('Xfile1', @%)
    265  bw!
    266 
    267  " set the block number in a pointer entry to a negative number
    268  let b = copy(save_b)
    269  if v:true  " Nvim changed this field from a long to an int64_t
    270    let b[4104:4111] = little_endian ? 0z00000000.00000080 : 0z80000000.00000000
    271  else
    272    let b[4104:4107] = little_endian ? 0z00000080 : 0z80000000
    273  endif
    274  call writefile(b, sn)
    275  call assert_fails('recover Xfile1', 'E312:')
    276  call assert_equal('Xfile1', @%)
    277  call assert_equal(['???LINES MISSING'], getline(1, '$'))
    278  bw!
    279 
    280  " clear the data block ID
    281  let b = copy(save_b)
    282  let b[8192:8193] = 0z0000
    283  call writefile(b, sn)
    284  call assert_fails('recover Xfile1', 'E312:')
    285  call assert_equal('Xfile1', @%)
    286  call assert_equal(['???BLOCK MISSING'], getline(1, '$'))
    287  bw!
    288 
    289  " set the number of lines in the data block to zero
    290  let b = copy(save_b)
    291  if system_64bit
    292    let b[8208:8215] = 0z00000000.00000000
    293  else
    294    let b[8208:8211] = 0z00000000
    295  endif
    296  call writefile(b, sn)
    297  call assert_fails('recover Xfile1', 'E312:')
    298  call assert_equal('Xfile1', @%)
    299  call assert_equal(['??? from here until ???END lines may have been inserted/deleted',
    300        \ '???END'], getline(1, '$'))
    301  bw!
    302 
    303  " set the number of lines in the data block to a large value
    304  let b = copy(save_b)
    305  if system_64bit
    306    let b[8208:8215] = 0z00FFFFFF.FFFFFF00
    307  else
    308    let b[8208:8211] = 0z00FFFF00
    309  endif
    310  call writefile(b, sn)
    311  call assert_fails('recover Xfile1', 'E312:')
    312  call assert_equal('Xfile1', @%)
    313  call assert_equal(['??? from here until ???END lines may have been inserted/deleted',
    314        \ '', '???', '??? lines may be missing',
    315        \ '???END'], getline(1, '$'))
    316  bw!
    317 
    318  " use an invalid text start for the lines in a data block
    319  let b = copy(save_b)
    320  if system_64bit
    321    let b[8216:8219] = 0z00000000
    322  else
    323    let b[8212:8215] = 0z00000000
    324  endif
    325  call writefile(b, sn)
    326  call assert_fails('recover Xfile1', 'E312:')
    327  call assert_equal('Xfile1', @%)
    328  call assert_equal(['???'], getline(1, '$'))
    329  bw!
    330 
    331  " use an incorrect text end (db_txt_end) for the data block
    332  let b = copy(save_b)
    333  let b[8204:8207] = little_endian ? 0z80000000 : 0z00000080
    334  call writefile(b, sn)
    335  call assert_fails('recover Xfile1', 'E312:')
    336  call assert_equal('Xfile1', @%)
    337  call assert_equal(['??? from here until ???END lines may be messed up', '',
    338        \ '???END'], getline(1, '$'))
    339  bw!
    340 
    341  " remove the data block
    342  let b = copy(save_b)
    343  call writefile(b[:8191], sn)
    344  call assert_fails('recover Xfile1', 'E312:')
    345  call assert_equal('Xfile1', @%)
    346  call assert_equal(['???MANY LINES MISSING'], getline(1, '$'))
    347 
    348  bw!
    349  call delete(sn)
    350 endfunc
    351 
    352 " Test for :recover using an encrypted swap file
    353 func Test_recover_encrypted_swap_file()
    354  CheckFeature cryptv
    355  CheckUnix
    356 
    357  " Recover an encrypted file from the swap file without the original file
    358  new Xfile1
    359  call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt')
    360  call setline(1, ['aaa', 'bbb', 'ccc'])
    361  preserve
    362  let b = readblob('.Xfile1.swp')
    363  call writefile(b, '.Xfile1.swm')
    364  bw!
    365  call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt')
    366  call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
    367  bw!
    368  call delete('.Xfile1.swm')
    369 
    370  " Recover an encrypted file from the swap file with the original file
    371  new Xfile1
    372  call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt')
    373  call setline(1, ['aaa', 'bbb', 'ccc'])
    374  update
    375  call setline(1, ['111', '222', '333'])
    376  preserve
    377  let b = readblob('.Xfile1.swp')
    378  call writefile(b, '.Xfile1.swm')
    379  bw!
    380  call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt')
    381  call assert_equal(['111', '222', '333'], getline(1, '$'))
    382  call assert_true(&modified)
    383  bw!
    384  call delete('.Xfile1.swm')
    385  call delete('Xfile1')
    386 endfunc
    387 
    388 " Test for :recover using an unreadable swap file
    389 func Test_recover_unreadable_swap_file()
    390  CheckUnix
    391  CheckNotRoot
    392  new Xfile1
    393  let b = readblob('.Xfile1.swp')
    394  call writefile(b, '.Xfile1.swm', 'D')
    395  bw!
    396  call setfperm('.Xfile1.swm', '-w-------')
    397  call assert_fails('recover Xfile1', 'E306:')
    398 endfunc
    399 
    400 " Test for using :recover when the original file and the swap file have the
    401 " same contents.
    402 func Test_recover_unmodified_file()
    403  CheckUnix
    404  call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1')
    405  edit Xfile1
    406  preserve
    407  let b = readblob('.Xfile1.swp')
    408  %bw!
    409  call writefile(b, '.Xfile1.swz', 'D')
    410  let msg = execute('recover Xfile1')
    411  call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
    412  call assert_false(&modified)
    413  call assert_match('Buffer contents equals file contents', msg)
    414  bw!
    415  call delete('Xfile1')
    416 endfunc
    417 
    418 " Test for recovering a file when editing a symbolically linked file
    419 func Test_recover_symbolic_link()
    420  CheckUnix
    421  call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1', 'D')
    422  silent !ln -s Xfile1 Xfile2
    423  edit Xfile2
    424  call assert_equal('.Xfile1.swp', fnamemodify(swapname(''), ':t'))
    425  preserve
    426  let b = readblob('.Xfile1.swp')
    427  %bw!
    428  call writefile([], 'Xfile1')
    429  call writefile(b, '.Xfile1.swp')
    430  silent! recover Xfile2
    431  call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
    432  call assert_true(&modified)
    433  update
    434  %bw!
    435  call assert_equal(['aaa', 'bbb', 'ccc'], readfile('Xfile1'))
    436  call delete('Xfile2')
    437  call delete('.Xfile1.swp')
    438 endfunc
    439 
    440 " Test for recovering a file when an autocmd moves the cursor to an invalid
    441 " line. This used to result in an internal error (E315) which is fixed
    442 " by 8.2.2966.
    443 func Test_recover_invalid_cursor_pos()
    444  call writefile([], 'Xfile1', 'D')
    445  edit Xfile1
    446  preserve
    447  let b = readblob('.Xfile1.swp')
    448  bw!
    449  augroup Test
    450    au!
    451    au BufReadPost Xfile1 normal! 3G
    452  augroup END
    453  call writefile(range(1, 3), 'Xfile1')
    454  call writefile(b, '.Xfile1.swp', 'D')
    455  try
    456    recover Xfile1
    457  catch /E308:/
    458    " this test is for the :E315 internal error.
    459    " ignore the 'E308: Original file may have been changed' error
    460  endtry
    461  redraw!
    462  augroup Test
    463    au!
    464  augroup END
    465  augroup! Test
    466 endfunc
    467 
    468 " Test for recovering a buffer without a name
    469 func Test_noname_buffer()
    470  new
    471  call setline(1, ['one', 'two'])
    472  preserve
    473  let sn = swapname('')
    474  let b = readblob(sn)
    475  bw!
    476  call writefile(b, sn, 'D')
    477  exe "recover " .. sn
    478  call assert_equal(['one', 'two'], getline(1, '$'))
    479 endfunc
    480 
    481 " Test for recovering a corrupted swap file, those caused a crash
    482 func Test_recover_corrupted_swap_file1()
    483  CheckUnix
    484  " only works correctly on 64bit Unix systems:
    485  if !has('nvim') && v:sizeoflong != 8 || !has('unix')
    486    throw 'Skipped: Corrupt Swap file sample requires a 64bit Unix build'
    487  endif
    488  " Test 1: Heap buffer-overflow
    489  new
    490  let sample = 'samples/recover-crash1.swp'
    491  let target = '.Xpoc1.swp'  " Xpoc1.swp (non-hidden) doesn't work in Nvim
    492  call filecopy(sample, target)
    493  try
    494    sil recover! Xpoc1
    495  catch /^Vim\%((\S\+)\)\=:E1364:/
    496  endtry
    497  let content = getline(1, '$')->join()
    498  call assert_match('???ILLEGAL BLOCK NUMBER', content)
    499  call delete(target)
    500  bw!
    501 "
    502 "  " Test 2: Segfault
    503  new
    504  let sample = 'samples/recover-crash2.swp'
    505  let target = '.Xpoc2.swp'  " Xpoc1.swp (non-hidden) doesn't work in Nvim
    506  call filecopy(sample, target)
    507  try
    508    sil recover! Xpoc2
    509  catch /^Vim\%((\S\+)\)\=:E1364:/
    510  endtry
    511  let content = getline(1, '$')->join()
    512  call assert_match('???ILLEGAL BLOCK NUMBER', content)
    513  call assert_match('???LINES MISSING', content)
    514  call delete(target)
    515  bw!
    516 endfunc
    517 
    518 " vim: shiftwidth=2 sts=2 expandtab