build: migrate test framework from plenary to busted

Problem: plenary.nvim is deprecated. The test suite depends on
plenary's async test runner and coroutine-based utilities, tying the
project to an unmaintained dependency. CI also tests against Neovim
0.8-0.11, which are no longer relevant.

Solution: replace plenary with busted + nlua (nvim -l). Convert all
async test patterns (a.wrap, a.util.sleep, a.util.scheduler) to
synchronous equivalents using vim.wait. Rename tests/ to spec/ to
follow busted convention. Replace the CI test matrix with
nvim-busted-action targeting stable/nightly only. Add .busted config,
luarocks test_dependencies, and update the nix devshell.
This commit is contained in:
Barrett Ruth 2026-02-22 00:26:54 -05:00
parent a4da206b67
commit 6be0148eef
Signed by: barrett
GPG key ID: A6C96C9349D2FC81
28 changed files with 257 additions and 298 deletions

9
.busted Normal file
View file

@ -0,0 +1,9 @@
return {
_all = {
lua = "nlua",
},
default = {
ROOT = { "./spec/" },
helper = "spec/minimal_init.lua",
},
}

View file

@ -1,16 +0,0 @@
#!/bin/bash
set -e
version="${NVIM_TAG-stable}"
dl_name="nvim-linux-x86_64.appimage"
# The appimage name changed in v0.10.4
if python -c 'from packaging.version import Version; import sys; sys.exit(not (Version(sys.argv[1]) < Version("v0.10.4")))' "$version" 2>/dev/null; then
dl_name="nvim.appimage"
fi
curl -sL "https://github.com/neovim/neovim/releases/download/${version}/${dl_name}" -o nvim.appimage
chmod +x nvim.appimage
./nvim.appimage --appimage-extract >/dev/null
rm -f nvim.appimage
mkdir -p ~/.local/share/nvim
mv squashfs-root ~/.local/share/nvim/appimage
sudo ln -s "$HOME/.local/share/nvim/appimage/AppRun" /usr/bin/nvim
/usr/bin/nvim --version

View file

@ -12,7 +12,7 @@ on:
jobs:
selene:
name: Selene
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: NTBBloodbath/selene-action@v1.0.0
@ -22,7 +22,7 @@ jobs:
stylua:
name: StyLua
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Stylua
@ -30,11 +30,11 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: v2.1.0
args: --check lua tests
args: --check lua spec
typecheck:
name: typecheck
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mrcjkb/lua-typecheck-action@v0
@ -45,24 +45,16 @@ jobs:
run_tests:
strategy:
fail-fast: false
matrix:
include:
- nvim_tag: v0.8.3
- nvim_tag: v0.9.4
- nvim_tag: v0.10.4
- nvim_tag: v0.11.0
nvim_version:
- stable
- nightly
name: Run tests
runs-on: ubuntu-22.04
env:
NVIM_TAG: ${{ matrix.nvim_tag }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Neovim and dependencies
run: |
bash ./.github/workflows/install_nvim.sh
- name: Run tests
run: |
bash ./run_tests.sh
- uses: nvim-neorocks/nvim-busted-action@v1
with:
nvim_version: ${{ matrix.nvim_version }}

1
.gitignore vendored
View file

@ -41,7 +41,6 @@ luac.out
.direnv/
.envrc
.testenv/
doc/tags
scripts/benchmark.nvim
perf/tmp/

View file

@ -11,13 +11,13 @@ all: lint test
## test: run tests
.PHONY: test
test:
./run_tests.sh
luarocks test --local
## lint: run selene and stylua
.PHONY: lint
lint:
selene --display-style quiet .
stylua --check lua tests
stylua --check lua spec
## profile: use LuaJIT profiler to profile the plugin
.PHONY: profile
@ -41,4 +41,4 @@ scripts/benchmark.nvim:
## clean: reset the repository to a clean state
.PHONY: clean
clean:
rm -rf .testenv perf/tmp profile.json
rm -rf perf/tmp profile.json

View file

@ -22,6 +22,10 @@
pkgs.prettier
pkgs.stylua
pkgs.selene
(pkgs.luajit.withPackages (ps: [
ps.busted
ps.nlua
]))
];
};
});

View file

@ -16,6 +16,15 @@ dependencies = {
'lua >= 5.1',
}
test_dependencies = {
'nlua',
'busted >= 2.1.1',
}
test = {
type = 'busted',
}
build = {
type = 'builtin',
}

View file

@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -e
mkdir -p ".testenv/config/nvim"
mkdir -p ".testenv/data/nvim"
mkdir -p ".testenv/state/nvim"
mkdir -p ".testenv/run/nvim"
mkdir -p ".testenv/cache/nvim"
PLUGINS=".testenv/data/nvim/site/pack/plugins/start"
if [ ! -e "$PLUGINS/plenary.nvim" ]; then
git clone --depth=1 https://github.com/nvim-lua/plenary.nvim.git "$PLUGINS/plenary.nvim"
else
(cd "$PLUGINS/plenary.nvim" && git pull)
fi
XDG_CONFIG_HOME=".testenv/config" \
XDG_DATA_HOME=".testenv/data" \
XDG_STATE_HOME=".testenv/state" \
XDG_RUNTIME_DIR=".testenv/run" \
XDG_CACHE_HOME=".testenv/cache" \
nvim --headless -u tests/minimal_init.lua \
-c "PlenaryBustedDirectory ${1-tests} { minimal_init = './tests/minimal_init.lua' }"
echo "Success"

View file

@ -1,14 +1,13 @@
require('plenary.async').tests.add_to_env()
local fs = require('oil.fs')
local oil = require('oil')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
a.describe('Alternate buffer', function()
describe('Alternate buffer', function()
after_each(function()
test_util.reset_editor()
end)
a.it('sets previous buffer as alternate', function()
it('sets previous buffer as alternate', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -16,20 +15,19 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('sets previous buffer as alternate when editing url file', function()
it('sets previous buffer as alternate when editing url file', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
local readme = fs.join(vim.fn.getcwd(), 'README.md')
vim.cmd.edit({ args = { 'oil://' .. fs.os_to_posix_path(readme) } })
-- We're gonna jump around to 2 different buffers
test_util.wait_for_autocmd('BufEnter')
test_util.wait_for_autocmd('BufEnter')
assert.equals(readme, vim.api.nvim_buf_get_name(0))
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('sets previous buffer as alternate when editing oil://', function()
it('sets previous buffer as alternate when editing oil://', function()
vim.cmd.edit({ args = { 'foo' } })
vim.cmd.edit({ args = { 'oil://' .. fs.os_to_posix_path(vim.fn.getcwd()) } })
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -37,7 +35,7 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('preserves alternate buffer if editing the same file', function()
it('preserves alternate buffer if editing the same file', function()
vim.cmd.edit({ args = { 'foo' } })
vim.cmd.edit({ args = { 'bar' } })
oil.open()
@ -46,7 +44,7 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('preserves alternate buffer if discarding changes', function()
it('preserves alternate buffer if discarding changes', function()
vim.cmd.edit({ args = { 'foo' } })
vim.cmd.edit({ args = { 'bar' } })
oil.open()
@ -56,7 +54,7 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('sets previous buffer as alternate after multi-dir hops', function()
it('sets previous buffer as alternate after multi-dir hops', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -70,7 +68,7 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('sets previous buffer as alternate when inside oil buffer', function()
it('sets previous buffer as alternate when inside oil buffer', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -81,7 +79,7 @@ a.describe('Alternate buffer', function()
assert.equals('bar', vim.fn.expand('#'))
end)
a.it('preserves alternate when traversing oil dirs', function()
it('preserves alternate when traversing oil dirs', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -95,7 +93,7 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('preserves alternate when opening preview', function()
it('preserves alternate when opening preview', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -109,31 +107,27 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.describe('floating window', function()
a.it('sets previous buffer as alternate', function()
describe('floating window', function()
it('sets previous buffer as alternate', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open_float()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
-- This is lazy, but testing the actual select logic is more difficult. We can simply
-- replicate it by closing the current window and then doing the edit
vim.api.nvim_win_close(0, true)
vim.cmd.edit({ args = { 'bar' } })
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('preserves alternate buffer if editing the same file', function()
it('preserves alternate buffer if editing the same file', function()
vim.cmd.edit({ args = { 'foo' } })
vim.cmd.edit({ args = { 'bar' } })
oil.open_float()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
-- This is lazy, but testing the actual select logic is more difficult. We can simply
-- replicate it by closing the current window and then doing the edit
vim.api.nvim_win_close(0, true)
vim.cmd.edit({ args = { 'bar' } })
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('preserves alternate buffer if discarding changes', function()
it('preserves alternate buffer if discarding changes', function()
vim.cmd.edit({ args = { 'foo' } })
vim.cmd.edit({ args = { 'bar' } })
oil.open_float()
@ -142,7 +136,7 @@ a.describe('Alternate buffer', function()
assert.equals('foo', vim.fn.expand('#'))
end)
a.it('preserves alternate when traversing to a new file', function()
it('preserves alternate when traversing to a new file', function()
vim.cmd.edit({ args = { 'foo' } })
oil.open_float()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })

View file

@ -1,16 +1,15 @@
require('plenary.async').tests.add_to_env()
local oil = require('oil')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
a.describe('close', function()
a.before_each(function()
describe('close', function()
before_each(function()
test_util.reset_editor()
end)
a.after_each(function()
after_each(function()
test_util.reset_editor()
end)
a.it('does not close buffer from visual mode', function()
it('does not close buffer from visual mode', function()
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
assert.equals('oil', vim.bo.filetype)
@ -20,22 +19,22 @@ a.describe('close', function()
test_util.feedkeys({ '<Esc>' }, 10)
end)
a.it('does not close buffer from operator-pending mode', function()
it('does not close buffer from operator-pending mode', function()
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
assert.equals('oil', vim.bo.filetype)
vim.api.nvim_feedkeys('d', 'n', false)
a.util.sleep(20)
vim.wait(20)
local mode = vim.api.nvim_get_mode().mode
if mode:match('^no') then
oil.close()
assert.equals('oil', vim.bo.filetype)
end
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Esc>', true, true, true), 'n', false)
a.util.sleep(20)
vim.wait(20)
end)
a.it('closes buffer from normal mode', function()
it('closes buffer from normal mode', function()
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
assert.equals('oil', vim.bo.filetype)

View file

@ -1,21 +1,20 @@
require('plenary.async').tests.add_to_env()
local TmpDir = require('tests.tmpdir')
local TmpDir = require('spec.tmpdir')
local files = require('oil.adapters.files')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
a.describe('files adapter', function()
describe('files adapter', function()
local tmpdir
a.before_each(function()
before_each(function()
tmpdir = TmpDir.new()
end)
a.after_each(function()
after_each(function()
if tmpdir then
tmpdir:dispose()
end
test_util.reset_editor()
end)
a.it('tmpdir creates files and asserts they exist', function()
it('tmpdir creates files and asserts they exist', function()
tmpdir:create({ 'a.txt', 'foo/b.txt', 'foo/c.txt', 'bar/' })
tmpdir:assert_fs({
['a.txt'] = 'a.txt',
@ -25,8 +24,8 @@ a.describe('files adapter', function()
})
end)
a.it('Creates files', function()
local err = a.wrap(files.perform_action, 2)({
it('Creates files', function()
local err = test_util.await(files.perform_action, 2, {
url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt',
entry_type = 'file',
type = 'create',
@ -37,8 +36,8 @@ a.describe('files adapter', function()
})
end)
a.it('Creates directories', function()
local err = a.wrap(files.perform_action, 2)({
it('Creates directories', function()
local err = test_util.await(files.perform_action, 2, {
url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a',
entry_type = 'directory',
type = 'create',
@ -49,11 +48,10 @@ a.describe('files adapter', function()
})
end)
a.it('Deletes files', function()
it('Deletes files', function()
tmpdir:create({ 'a.txt' })
a.util.scheduler()
local url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
local err = a.wrap(files.perform_action, 2)({
local err = test_util.await(files.perform_action, 2, {
url = url,
entry_type = 'file',
type = 'delete',
@ -62,10 +60,10 @@ a.describe('files adapter', function()
tmpdir:assert_fs({})
end)
a.it('Deletes directories', function()
it('Deletes directories', function()
tmpdir:create({ 'a/' })
local url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a'
local err = a.wrap(files.perform_action, 2)({
local err = test_util.await(files.perform_action, 2, {
url = url,
entry_type = 'directory',
type = 'delete',
@ -74,12 +72,11 @@ a.describe('files adapter', function()
tmpdir:assert_fs({})
end)
a.it('Moves files', function()
it('Moves files', function()
tmpdir:create({ 'a.txt' })
a.util.scheduler()
local src_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
local dest_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b.txt'
local err = a.wrap(files.perform_action, 2)({
local err = test_util.await(files.perform_action, 2, {
src_url = src_url,
dest_url = dest_url,
entry_type = 'file',
@ -91,12 +88,11 @@ a.describe('files adapter', function()
})
end)
a.it('Moves directories', function()
it('Moves directories', function()
tmpdir:create({ 'a/a.txt' })
a.util.scheduler()
local src_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a'
local dest_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b'
local err = a.wrap(files.perform_action, 2)({
local err = test_util.await(files.perform_action, 2, {
src_url = src_url,
dest_url = dest_url,
entry_type = 'directory',
@ -109,12 +105,11 @@ a.describe('files adapter', function()
})
end)
a.it('Copies files', function()
it('Copies files', function()
tmpdir:create({ 'a.txt' })
a.util.scheduler()
local src_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a.txt'
local dest_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b.txt'
local err = a.wrap(files.perform_action, 2)({
local err = test_util.await(files.perform_action, 2, {
src_url = src_url,
dest_url = dest_url,
entry_type = 'file',
@ -127,12 +122,11 @@ a.describe('files adapter', function()
})
end)
a.it('Recursively copies directories', function()
it('Recursively copies directories', function()
tmpdir:create({ 'a/a.txt' })
a.util.scheduler()
local src_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'a'
local dest_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. 'b'
local err = a.wrap(files.perform_action, 2)({
local err = test_util.await(files.perform_action, 2, {
src_url = src_url,
dest_url = dest_url,
entry_type = 'directory',
@ -147,7 +141,7 @@ a.describe('files adapter', function()
})
end)
a.it('Editing a new oil://path/ creates an oil buffer', function()
it('Editing a new oil://path/ creates an oil buffer', function()
local tmpdir_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. '/'
vim.cmd.edit({ args = { tmpdir_url } })
test_util.wait_oil_ready()
@ -155,11 +149,10 @@ a.describe('files adapter', function()
vim.cmd.edit({ args = { new_url } })
test_util.wait_oil_ready()
assert.equals('oil', vim.bo.filetype)
-- The normalization will add a '/'
assert.equals(new_url .. '/', vim.api.nvim_buf_get_name(0))
end)
a.it('Editing a new oil://file.rb creates a normal buffer', function()
it('Editing a new oil://file.rb creates a normal buffer', function()
local tmpdir_url = 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') .. '/'
vim.cmd.edit({ args = { tmpdir_url } })
test_util.wait_for_autocmd('BufReadPost')

8
spec/minimal_init.lua Normal file
View file

@ -0,0 +1,8 @@
vim.cmd([[set runtimepath=$VIMRUNTIME]])
vim.opt.runtimepath:append('.')
vim.opt.packpath = {}
vim.o.swapfile = false
vim.fn.mkdir(vim.fn.stdpath('cache'), 'p')
vim.fn.mkdir(vim.fn.stdpath('data'), 'p')
vim.fn.mkdir(vim.fn.stdpath('state'), 'p')
require('spec.test_util').reset_editor()

View file

@ -1,5 +1,5 @@
local fs = require('oil.fs')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
local util = require('oil.util')
describe('update_moved_buffers', function()

View file

@ -1,15 +1,14 @@
require('plenary.async').tests.add_to_env()
local cache = require('oil.cache')
local constants = require('oil.constants')
local mutator = require('oil.mutator')
local test_adapter = require('oil.adapters.test')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
local FIELD_ID = constants.FIELD_ID
local FIELD_NAME = constants.FIELD_NAME
local FIELD_TYPE = constants.FIELD_TYPE
a.describe('mutator', function()
describe('mutator', function()
after_each(function()
test_util.reset_editor()
end)
@ -196,9 +195,6 @@ a.describe('mutator', function()
end)
it('Handles parent child move ordering', function()
-- move parent into a child and child OUT of parent
-- MOVE /a/b -> /b
-- MOVE /a -> /b/a
local move1 = {
type = 'move',
src_url = 'oil-test:///a/b',
@ -217,9 +213,6 @@ a.describe('mutator', function()
end)
it('Handles a delete inside a moved folder', function()
-- delete in directory and move directory
-- DELETE /a/b.txt
-- MOVE /a/ -> /b/
local del = {
type = 'delete',
url = 'oil-test:///a/b.txt',
@ -346,12 +339,12 @@ a.describe('mutator', function()
end)
end)
a.describe('perform actions', function()
a.it('creates new entries', function()
describe('perform actions', function()
it('creates new entries', function()
local actions = {
{ type = 'create', url = 'oil-test:///a.txt', entry_type = 'file' },
}
a.wrap(mutator.process_actions, 2)(actions)
test_util.await(mutator.process_actions, 2, actions)
local files = cache.list_url('oil-test:///')
assert.are.same({
['a.txt'] = {
@ -362,12 +355,12 @@ a.describe('mutator', function()
}, files)
end)
a.it('deletes entries', function()
it('deletes entries', function()
local file = test_adapter.test_set('/a.txt', 'file')
local actions = {
{ type = 'delete', url = 'oil-test:///a.txt', entry_type = 'file' },
}
a.wrap(mutator.process_actions, 2)(actions)
test_util.await(mutator.process_actions, 2, actions)
local files = cache.list_url('oil-test:///')
assert.are.same({}, files)
assert.is_nil(cache.get_entry_by_id(file[FIELD_ID]))
@ -376,7 +369,7 @@ a.describe('mutator', function()
end)
end)
a.it('moves entries', function()
it('moves entries', function()
local file = test_adapter.test_set('/a.txt', 'file')
local actions = {
{
@ -386,7 +379,7 @@ a.describe('mutator', function()
entry_type = 'file',
},
}
a.wrap(mutator.process_actions, 2)(actions)
test_util.await(mutator.process_actions, 2, actions)
local files = cache.list_url('oil-test:///')
local new_entry = {
[FIELD_ID] = file[FIELD_ID],
@ -400,7 +393,7 @@ a.describe('mutator', function()
assert.equals('oil-test:///', cache.get_parent_url(file[FIELD_ID]))
end)
a.it('copies entries', function()
it('copies entries', function()
local file = test_adapter.test_set('/a.txt', 'file')
local actions = {
{
@ -410,7 +403,7 @@ a.describe('mutator', function()
entry_type = 'file',
},
}
a.wrap(mutator.process_actions, 2)(actions)
test_util.await(mutator.process_actions, 2, actions)
local files = cache.list_url('oil-test:///')
local new_entry = {
[FIELD_ID] = file[FIELD_ID] + 1,

View file

@ -1,8 +1,7 @@
require('plenary.async').tests.add_to_env()
local constants = require('oil.constants')
local parser = require('oil.mutator.parser')
local test_adapter = require('oil.adapters.test')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
local util = require('oil.util')
local view = require('oil.view')

View file

@ -1,25 +1,24 @@
require('plenary.async').tests.add_to_env()
local TmpDir = require('tests.tmpdir')
local TmpDir = require('spec.tmpdir')
local oil = require('oil')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
local util = require('oil.util')
a.describe('oil preview', function()
describe('oil preview', function()
local tmpdir
a.before_each(function()
before_each(function()
tmpdir = TmpDir.new()
end)
a.after_each(function()
after_each(function()
if tmpdir then
tmpdir:dispose()
end
test_util.reset_editor()
end)
a.it('opens preview window', function()
it('opens preview window', function()
tmpdir:create({ 'a.txt' })
test_util.oil_open(tmpdir.path)
a.wrap(oil.open_preview, 2)()
test_util.await(oil.open_preview, 2)
local preview_win = util.get_preview_win()
assert.not_nil(preview_win)
assert(preview_win)
@ -28,7 +27,7 @@ a.describe('oil preview', function()
assert.are.same({ 'a.txt' }, preview_lines)
end)
a.it('opens preview window when open(preview={})', function()
it('opens preview window when open(preview={})', function()
tmpdir:create({ 'a.txt' })
test_util.oil_open(tmpdir.path, { preview = {} })
local preview_win = util.get_preview_win()

View file

@ -1,16 +1,15 @@
require('plenary.async').tests.add_to_env()
local TmpDir = require('tests.tmpdir')
local TmpDir = require('spec.tmpdir')
local actions = require('oil.actions')
local oil = require('oil')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
local view = require('oil.view')
a.describe('regression tests', function()
describe('regression tests', function()
local tmpdir
a.before_each(function()
before_each(function()
tmpdir = TmpDir.new()
end)
a.after_each(function()
after_each(function()
if tmpdir then
tmpdir:dispose()
tmpdir = nil
@ -18,8 +17,7 @@ a.describe('regression tests', function()
test_util.reset_editor()
end)
-- see https://github.com/stevearc/oil.nvim/issues/25
a.it('can edit dirs that will be renamed to an existing buffer', function()
it('can edit dirs that will be renamed to an existing buffer', function()
vim.cmd.edit({ args = { 'README.md' } })
vim.cmd.vsplit()
vim.cmd.edit({ args = { '%:p:h' } })
@ -31,8 +29,7 @@ a.describe('regression tests', function()
assert.equals('oil', vim.bo.filetype)
end)
-- https://github.com/stevearc/oil.nvim/issues/37
a.it('places the cursor on correct entry when opening on file', function()
it('places the cursor on correct entry when opening on file', function()
vim.cmd.edit({ args = { '.' } })
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
local entry = oil.get_cursor_entry()
@ -46,8 +43,7 @@ a.describe('regression tests', function()
assert.equals('README.md', entry and entry.name)
end)
-- https://github.com/stevearc/oil.nvim/issues/64
a.it("doesn't close floating windows oil didn't open itself", function()
it("doesn't close floating windows oil didn't open itself", function()
local winid = vim.api.nvim_open_win(vim.fn.bufadd('README.md'), true, {
relative = 'editor',
row = 1,
@ -56,29 +52,27 @@ a.describe('regression tests', function()
height = 100,
})
oil.open()
a.util.sleep(10)
vim.wait(10)
oil.close()
a.util.sleep(10)
vim.wait(10)
assert.equals(winid, vim.api.nvim_get_current_win())
end)
-- https://github.com/stevearc/oil.nvim/issues/64
a.it("doesn't close splits on oil.close", function()
it("doesn't close splits on oil.close", function()
vim.cmd.edit({ args = { 'README.md' } })
vim.cmd.vsplit()
local winid = vim.api.nvim_get_current_win()
local bufnr = vim.api.nvim_get_current_buf()
oil.open()
a.util.sleep(10)
vim.wait(10)
oil.close()
a.util.sleep(10)
vim.wait(10)
assert.equals(2, #vim.api.nvim_tabpage_list_wins(0))
assert.equals(winid, vim.api.nvim_get_current_win())
assert.equals(bufnr, vim.api.nvim_get_current_buf())
end)
-- https://github.com/stevearc/oil.nvim/issues/79
a.it('Returns to empty buffer on close', function()
it('Returns to empty buffer on close', function()
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
oil.close()
@ -86,9 +80,8 @@ a.describe('regression tests', function()
assert.equals('', vim.api.nvim_buf_get_name(0))
end)
a.it('All buffers set nomodified after save', function()
it('All buffers set nomodified after save', function()
tmpdir:create({ 'a.txt' })
a.util.scheduler()
vim.cmd.edit({ args = { 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') } })
local first_dir = vim.api.nvim_get_current_buf()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
@ -97,7 +90,6 @@ a.describe('regression tests', function()
return vim.bo.modifiable
end, 10)
test_util.feedkeys({ 'p' }, 10)
a.util.scheduler()
oil.save({ confirm = false })
vim.wait(1000, function()
return vim.bo.modifiable
@ -105,11 +97,10 @@ a.describe('regression tests', function()
tmpdir:assert_fs({
['test/a.txt'] = 'a.txt',
})
-- The first oil buffer should not be modified anymore
assert.falsy(vim.bo[first_dir].modified)
end)
a.it("refreshing buffer doesn't lose track of it", function()
it("refreshing buffer doesn't lose track of it", function()
vim.cmd.edit({ args = { '.' } })
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
local bufnr = vim.api.nvim_get_current_buf()
@ -118,7 +109,7 @@ a.describe('regression tests', function()
assert.are.same({ bufnr }, require('oil.view').get_all_buffers())
end)
a.it('can copy a file multiple times', function()
it('can copy a file multiple times', function()
test_util.actions.open({ tmpdir.path })
vim.api.nvim_feedkeys('ifoo.txt', 'x', true)
test_util.actions.save()
@ -133,10 +124,8 @@ a.describe('regression tests', function()
})
end)
-- https://github.com/stevearc/oil.nvim/issues/355
a.it('can open files from floating window', function()
it('can open files from floating window', function()
tmpdir:create({ 'a.txt' })
a.util.scheduler()
oil.open_float(tmpdir.path)
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
actions.select.callback()

View file

@ -1,90 +1,85 @@
require('plenary.async').tests.add_to_env()
local oil = require('oil')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
a.describe('oil select', function()
describe('oil select', function()
after_each(function()
test_util.reset_editor()
end)
a.it('opens file under cursor', function()
it('opens file under cursor', function()
test_util.oil_open()
-- Go to the bottom, so the cursor is not on a directory
vim.cmd.normal({ args = { 'G' } })
a.wrap(oil.select, 2)()
test_util.await(oil.select, 2)
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
assert.not_equals('oil', vim.bo.filetype)
end)
a.it('opens file in new tab', function()
it('opens file in new tab', function()
test_util.oil_open()
local tabpage = vim.api.nvim_get_current_tabpage()
a.wrap(oil.select, 2)({ tab = true })
test_util.await(oil.select, 2, { tab = true })
assert.equals(2, #vim.api.nvim_list_tabpages())
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
assert.not_equals(tabpage, vim.api.nvim_get_current_tabpage())
end)
a.it('opens file in new split', function()
it('opens file in new split', function()
test_util.oil_open()
local winid = vim.api.nvim_get_current_win()
a.wrap(oil.select, 2)({ vertical = true })
test_util.await(oil.select, 2, { vertical = true })
assert.equals(1, #vim.api.nvim_list_tabpages())
assert.equals(2, #vim.api.nvim_tabpage_list_wins(0))
assert.not_equals(winid, vim.api.nvim_get_current_win())
end)
a.it('opens multiple files in new tabs', function()
it('opens multiple files in new tabs', function()
test_util.oil_open()
vim.api.nvim_feedkeys('Vj', 'x', true)
local tabpage = vim.api.nvim_get_current_tabpage()
a.wrap(oil.select, 2)({ tab = true })
test_util.await(oil.select, 2, { tab = true })
assert.equals(3, #vim.api.nvim_list_tabpages())
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
assert.not_equals(tabpage, vim.api.nvim_get_current_tabpage())
end)
a.it('opens multiple files in new splits', function()
it('opens multiple files in new splits', function()
test_util.oil_open()
vim.api.nvim_feedkeys('Vj', 'x', true)
local winid = vim.api.nvim_get_current_win()
a.wrap(oil.select, 2)({ vertical = true })
test_util.await(oil.select, 2, { vertical = true })
assert.equals(1, #vim.api.nvim_list_tabpages())
assert.equals(3, #vim.api.nvim_tabpage_list_wins(0))
assert.not_equals(winid, vim.api.nvim_get_current_win())
end)
a.describe('close after open', function()
a.it('same window', function()
describe('close after open', function()
it('same window', function()
vim.cmd.edit({ args = { 'foo' } })
local bufnr = vim.api.nvim_get_current_buf()
test_util.oil_open()
-- Go to the bottom, so the cursor is not on a directory
vim.cmd.normal({ args = { 'G' } })
a.wrap(oil.select, 2)({ close = true })
test_util.await(oil.select, 2, { close = true })
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
-- This one we actually don't expect the buffer to be the same as the initial buffer, because
-- we opened a file
assert.not_equals(bufnr, vim.api.nvim_get_current_buf())
assert.not_equals('oil', vim.bo.filetype)
end)
a.it('split', function()
it('split', function()
vim.cmd.edit({ args = { 'foo' } })
local bufnr = vim.api.nvim_get_current_buf()
local winid = vim.api.nvim_get_current_win()
test_util.oil_open()
a.wrap(oil.select, 2)({ vertical = true, close = true })
test_util.await(oil.select, 2, { vertical = true, close = true })
assert.equals(2, #vim.api.nvim_tabpage_list_wins(0))
assert.equals(bufnr, vim.api.nvim_win_get_buf(winid))
end)
a.it('tab', function()
it('tab', function()
vim.cmd.edit({ args = { 'foo' } })
local bufnr = vim.api.nvim_get_current_buf()
local tabpage = vim.api.nvim_get_current_tabpage()
test_util.oil_open()
a.wrap(oil.select, 2)({ tab = true, close = true })
test_util.await(oil.select, 2, { tab = true, close = true })
assert.equals(1, #vim.api.nvim_tabpage_list_wins(0))
assert.equals(2, #vim.api.nvim_list_tabpages())
vim.api.nvim_set_current_tabpage(tabpage)

View file

@ -1,4 +1,3 @@
require('plenary.async').tests.add_to_env()
local cache = require('oil.cache')
local test_adapter = require('oil.adapters.test')
local util = require('oil.util')
@ -34,15 +33,36 @@ local function throwiferr(err, ...)
end
end
M.oil_open = function(...)
a.wrap(require('oil').open, 3)(...)
end
M.await = function(fn, nargs, ...)
return throwiferr(a.wrap(fn, nargs)(...))
local done = false
local results
local n_results = 0
local args = { ... }
args[nargs] = function(...)
results = { ... }
n_results = select('#', ...)
done = true
end
fn(unpack(args, 1, nargs))
vim.wait(10000, function()
return done
end, 10)
if not done then
error('M.await timed out')
end
return unpack(results, 1, n_results)
end
M.wait_for_autocmd = a.wrap(function(autocmd, cb)
M.await_throwiferr = function(fn, nargs, ...)
return throwiferr(M.await(fn, nargs, ...))
end
M.oil_open = function(...)
M.await(require('oil').open, 3, ...)
end
M.wait_for_autocmd = function(autocmd)
local triggered = false
local opts = {
pattern = '*',
nested = true,
@ -53,30 +73,47 @@ M.wait_for_autocmd = a.wrap(function(autocmd, cb)
autocmd = autocmd[1]
opts[1] = nil
end
opts.callback = vim.schedule_wrap(cb)
opts.callback = vim.schedule_wrap(function()
triggered = true
end)
vim.api.nvim_create_autocmd(autocmd, opts)
end, 2)
vim.wait(10000, function()
return triggered
end, 10)
if not triggered then
error('wait_for_autocmd timed out waiting for ' .. tostring(autocmd))
end
end
M.wait_oil_ready = a.wrap(function(cb)
util.run_after_load(0, vim.schedule_wrap(cb))
end, 1)
M.wait_oil_ready = function()
local ready = false
util.run_after_load(
0,
vim.schedule_wrap(function()
ready = true
end)
)
vim.wait(10000, function()
return ready
end, 10)
if not ready then
error('wait_oil_ready timed out')
end
end
---@param actions string[]
---@param timestep integer
M.feedkeys = function(actions, timestep)
timestep = timestep or 10
a.util.sleep(timestep)
vim.wait(timestep)
for _, action in ipairs(actions) do
a.util.sleep(timestep)
vim.wait(timestep)
local escaped = vim.api.nvim_replace_termcodes(action, true, false, true)
vim.api.nvim_feedkeys(escaped, 'm', true)
end
a.util.sleep(timestep)
-- process pending keys until the queue is empty.
-- Note that this will exit insert mode.
vim.wait(timestep)
vim.api.nvim_feedkeys('', 'x', true)
a.util.sleep(timestep)
vim.wait(timestep)
end
M.actions = {
@ -85,7 +122,6 @@ M.actions = {
open = function(args)
vim.schedule(function()
vim.cmd.Oil({ args = args })
-- If this buffer was already open, manually dispatch the autocmd to finish the wait
if vim.b.oil_ready then
vim.api.nvim_exec_autocmds('User', {
pattern = 'OilEnter',

View file

@ -1,25 +1,18 @@
local fs = require('oil.fs')
local test_util = require('tests.test_util')
local await = test_util.await
local test_util = require('spec.test_util')
---@param path string
---@param cb fun(err: nil|string)
local function touch(path, cb)
vim.loop.fs_open(path, 'w', 420, function(err, fd) -- 0644
if err then
cb(err)
else
local function touch(path)
local fd, open_err = vim.loop.fs_open(path, 'w', 420) -- 0644
if not fd then
error(open_err)
end
local shortpath = path:gsub('^[^' .. fs.sep .. ']*' .. fs.sep, '')
vim.loop.fs_write(fd, shortpath, nil, function(err2)
if err2 then
cb(err2)
else
vim.loop.fs_close(fd, cb)
local _, write_err = vim.loop.fs_write(fd, shortpath)
if write_err then
error(write_err)
end
end)
end
end)
vim.loop.fs_close(fd)
end
---@param filepath string
@ -28,11 +21,14 @@ local function exists(filepath)
local stat = vim.loop.fs_stat(filepath)
return stat ~= nil and stat.type ~= nil
end
local TmpDir = {}
TmpDir.new = function()
local path = await(vim.loop.fs_mkdtemp, 2, 'oil_test_XXXXXXXXX')
a.util.scheduler()
local path, err = vim.loop.fs_mkdtemp('oil_test_XXXXXXXXX')
if not path then
error(err)
end
return setmetatable({ path = path }, {
__index = TmpDir,
})
@ -46,13 +42,12 @@ function TmpDir:create(paths)
for i, piece in ipairs(pieces) do
partial_path = fs.join(partial_path, piece)
if i == #pieces and not vim.endswith(partial_path, fs.sep) then
await(touch, 2, partial_path)
touch(partial_path)
elseif not exists(partial_path) then
vim.loop.fs_mkdir(partial_path, 493)
end
end
end
a.util.scheduler()
end
---@param filepath string
@ -65,12 +60,10 @@ local read_file = function(filepath)
local stat = vim.loop.fs_fstat(fd)
local content = vim.loop.fs_read(fd, stat.size)
vim.loop.fs_close(fd)
a.util.scheduler()
return content
end
---@param dir string
---@param cb fun(err: nil|string, entry: {type: oil.EntryType, name: string, root: string}
local function walk(dir)
local ret = {}
for name, type in vim.fs.dir(dir) do
@ -112,12 +105,11 @@ local assert_fs = function(root, paths)
end
local expected_content = paths[shortpath]
paths[shortpath] = nil
assert.truthy(expected_content, string.format("Unexpected entry '%s'", shortpath))
assert(expected_content, string.format("Unexpected entry '%s'", shortpath))
if entry.type == 'file' then
local data = read_file(fullpath)
assert.equals(
expected_content,
data,
assert(
expected_content == data,
string.format(
"File '%s' expected content '%s' received '%s'",
shortpath,
@ -129,8 +121,8 @@ local assert_fs = function(root, paths)
end
for k, v in pairs(paths) do
assert.falsy(
k,
assert(
not k,
string.format(
"Expected %s '%s', but it was not found",
v == true and 'directory' or 'file',
@ -142,27 +134,23 @@ end
---@param paths table<string, string>
function TmpDir:assert_fs(paths)
a.util.scheduler()
assert_fs(self.path, paths)
end
function TmpDir:assert_exists(path)
a.util.scheduler()
path = fs.join(self.path, path)
local stat = vim.loop.fs_stat(path)
assert.truthy(stat, string.format("Expected path '%s' to exist", path))
assert(stat, string.format("Expected path '%s' to exist", path))
end
function TmpDir:assert_not_exists(path)
a.util.scheduler()
path = fs.join(self.path, path)
local stat = vim.loop.fs_stat(path)
assert.falsy(stat, string.format("Expected path '%s' to not exist", path))
assert(not stat, string.format("Expected path '%s' to not exist", path))
end
function TmpDir:dispose()
await(fs.recursive_delete, 3, 'directory', self.path)
a.util.scheduler()
test_util.await_throwiferr(fs.recursive_delete, 3, 'directory', self.path)
end
return TmpDir

View file

@ -1,19 +1,18 @@
require('plenary.async').tests.add_to_env()
local TmpDir = require('tests.tmpdir')
local test_util = require('tests.test_util')
local TmpDir = require('spec.tmpdir')
local test_util = require('spec.test_util')
a.describe('freedesktop', function()
describe('freedesktop', function()
local tmpdir
local tmphome
local home = vim.env.XDG_DATA_HOME
a.before_each(function()
before_each(function()
require('oil.config').delete_to_trash = true
tmpdir = TmpDir.new()
tmphome = TmpDir.new()
package.loaded['oil.adapters.trash'] = require('oil.adapters.trash.freedesktop')
vim.env.XDG_DATA_HOME = tmphome.path
end)
a.after_each(function()
after_each(function()
vim.env.XDG_DATA_HOME = home
if tmpdir then
tmpdir:dispose()
@ -25,7 +24,7 @@ a.describe('freedesktop', function()
package.loaded['oil.adapters.trash'] = nil
end)
a.it('files can be moved to the trash', function()
it('files can be moved to the trash', function()
tmpdir:create({ 'a.txt', 'foo/b.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -39,7 +38,7 @@ a.describe('freedesktop', function()
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
end)
a.it('deleting a file moves it to trash', function()
it('deleting a file moves it to trash', function()
tmpdir:create({ 'a.txt', 'foo/b.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -51,7 +50,7 @@ a.describe('freedesktop', function()
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
end)
a.it('deleting a directory moves it to trash', function()
it('deleting a directory moves it to trash', function()
tmpdir:create({ 'a.txt', 'foo/b.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('foo/')
@ -63,7 +62,7 @@ a.describe('freedesktop', function()
assert.are.same({ 'foo/' }, test_util.parse_entries(0))
end)
a.it('deleting a file from trash deletes it permanently', function()
it('deleting a file from trash deletes it permanently', function()
tmpdir:create({ 'a.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -78,7 +77,7 @@ a.describe('freedesktop', function()
assert.are.same({}, test_util.parse_entries(0))
end)
a.it('cannot create files in the trash', function()
it('cannot create files in the trash', function()
tmpdir:create({ 'a.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -91,7 +90,7 @@ a.describe('freedesktop', function()
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
end)
a.it('cannot rename files in the trash', function()
it('cannot rename files in the trash', function()
tmpdir:create({ 'a.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -104,7 +103,7 @@ a.describe('freedesktop', function()
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
end)
a.it('cannot copy files in the trash', function()
it('cannot copy files in the trash', function()
tmpdir:create({ 'a.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -117,7 +116,7 @@ a.describe('freedesktop', function()
assert.are.same({ 'a.txt' }, test_util.parse_entries(0))
end)
a.it('can restore files from trash', function()
it('can restore files from trash', function()
tmpdir:create({ 'a.txt' })
test_util.actions.open({ tmpdir.path })
test_util.actions.focus('a.txt')
@ -135,7 +134,7 @@ a.describe('freedesktop', function()
})
end)
a.it('can have multiple files with the same name in trash', function()
it('can have multiple files with the same name in trash', function()
tmpdir:create({ 'a.txt' })
test_util.actions.open({ tmpdir.path })
vim.api.nvim_feedkeys('dd', 'x', true)

View file

@ -1,13 +1,12 @@
require('plenary.async').tests.add_to_env()
local oil = require('oil')
local test_util = require('tests.test_util')
local test_util = require('spec.test_util')
a.describe('window options', function()
describe('window options', function()
after_each(function()
test_util.reset_editor()
end)
a.it('Restores window options on close', function()
it('Restores window options on close', function()
vim.cmd.edit({ args = { 'README.md' } })
test_util.oil_open()
assert.equals('no', vim.o.signcolumn)
@ -15,21 +14,21 @@ a.describe('window options', function()
assert.equals('auto', vim.o.signcolumn)
end)
a.it('Restores window options on edit', function()
it('Restores window options on edit', function()
test_util.oil_open()
assert.equals('no', vim.o.signcolumn)
vim.cmd.edit({ args = { 'README.md' } })
assert.equals('auto', vim.o.signcolumn)
end)
a.it('Restores window options on split <filename>', function()
it('Restores window options on split <filename>', function()
test_util.oil_open()
assert.equals('no', vim.o.signcolumn)
vim.cmd.split({ args = { 'README.md' } })
assert.equals('auto', vim.o.signcolumn)
end)
a.it('Restores window options on split', function()
it('Restores window options on split', function()
test_util.oil_open()
assert.equals('no', vim.o.signcolumn)
vim.cmd.split()
@ -37,14 +36,14 @@ a.describe('window options', function()
assert.equals('auto', vim.o.signcolumn)
end)
a.it('Restores window options on tabnew <filename>', function()
it('Restores window options on tabnew <filename>', function()
test_util.oil_open()
assert.equals('no', vim.o.signcolumn)
vim.cmd.tabnew({ args = { 'README.md' } })
assert.equals('auto', vim.o.signcolumn)
end)
a.it('Restores window options on tabnew', function()
it('Restores window options on tabnew', function()
test_util.oil_open()
assert.equals('no', vim.o.signcolumn)
vim.cmd.tabnew()
@ -52,7 +51,7 @@ a.describe('window options', function()
assert.equals('auto', vim.o.signcolumn)
end)
a.it('Sets the window options when re-entering oil buffer', function()
it('Sets the window options when re-entering oil buffer', function()
oil.open()
test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' })
assert.truthy(vim.w.oil_did_enter)

View file

@ -1,5 +0,0 @@
vim.opt.runtimepath:append('.')
vim.o.swapfile = false
vim.bo.swapfile = false
require('tests.test_util').reset_editor()