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