Files
arti-pack/tests/lock_spec.lua
T
unkin-agent 460dc2c5c8
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
Install plugins from a branch
Many upstream plugins publish no tags at all, so a tag-only installer cannot
manage them.

- Add a branch spec field, mutually exclusive with tag and validated the same way
- Build archive/refs/heads/<branch> URLs for branch specs
- Refetch a branch only on :ArtiPackUpdate or a forced sync, never from cache
- Record the branch, archive digest and fetch time in the lockfile
- Report a branch plugin as tracked and fetched, never as up to date
2026-09-12 00:08:18 +10:00

185 lines
6.2 KiB
Lua

local t = require('tests.harness')
local Lock = require('arti-pack.lock')
local spec = require('arti-pack.spec')
local function tmpdir()
local dir = vim.fn.tempname()
vim.fn.mkdir(dir, 'p')
return dir
end
local function write(path, contents)
local fd = assert(io.open(path, 'w'))
fd:write(contents)
fd:close()
end
t.describe('lock', function()
t.it('round-trips entries through the state file', function()
local path = tmpdir() .. '/nested/lock.json'
local lock = Lock.new(path)
lock:set('plenary.nvim', { tag = 'v0.1.4', opt = false })
t.truthy(lock:save())
local reloaded = Lock.new(path):load()
t.eq({ tag = 'v0.1.4', opt = false }, reloaded:get('plenary.nvim'))
end)
t.it('round-trips a branch entry with what identifies the fetched content', function()
local path = tmpdir() .. '/lock.json'
local entry = {
branch = 'main',
digest = string.rep('a', 64),
installed_at = 1757000000,
opt = false,
source = 'hrsh7th/cmp-path',
ext = 'zip',
}
local lock = Lock.new(path)
lock:set('cmp-path', entry)
t.truthy(lock:save())
t.eq(entry, Lock.new(path):load():get('cmp-path'))
end)
t.it('starts empty when the state file does not exist', function()
local lock = Lock.new(tmpdir() .. '/absent.json'):load()
t.eq({}, lock:names())
end)
t.it('starts empty when the state file is corrupt', function()
local path = tmpdir() .. '/lock.json'
write(path, 'not json at all')
t.eq({}, Lock.new(path):load():names())
end)
t.it('starts empty when the state file has no plugins table', function()
local path = tmpdir() .. '/lock.json'
write(path, '{"version":1}')
t.eq({}, Lock.new(path):load():names())
end)
t.it('leaves no temporary file beside the state file', function()
local dir = tmpdir()
local lock = Lock.new(dir .. '/lock.json')
lock:set('plenary.nvim', { tag = 'v0.1.4' })
t.truthy(lock:save())
local names = {}
for _, entry in ipairs(vim.fn.readdir(dir)) do
table.insert(names, entry)
end
t.eq({ 'lock.json' }, names)
end)
t.it('lists names in sorted order', function()
local lock = Lock.new(tmpdir() .. '/lock.json')
lock:set('zebra', { tag = 'v1' })
lock:set('alpha', { tag = 'v1' })
t.eq({ 'alpha', 'zebra' }, lock:names())
end)
t.it('drops removed entries from a saved file', function()
local path = tmpdir() .. '/lock.json'
local lock = Lock.new(path)
lock:set('gone', { tag = 'v1' })
lock:set('kept', { tag = 'v1' })
lock:remove('gone')
lock:save()
t.eq({ 'kept' }, Lock.new(path):load():names())
end)
end)
t.describe('lock.is_current', function()
local function make(entry)
local lock = Lock.new(tmpdir() .. '/lock.json')
if entry then
lock:set('repo', entry)
end
return lock
end
t.it('is false for a plugin that was never installed', function()
local s = assert(spec.parse({ 'owner/repo', tag = 'v1' }))
t.falsy(make(nil):is_current(s))
end)
t.it('is true when the tag and load style both match', function()
local s = assert(spec.parse({ 'owner/repo', tag = 'v1' }))
t.truthy(make({ tag = 'v1', opt = false }):is_current(s))
end)
t.it('is false when the tag changed', function()
local s = assert(spec.parse({ 'owner/repo', tag = 'v2' }))
t.falsy(make({ tag = 'v1', opt = false }):is_current(s))
end)
t.it('is false when the plugin moved between start and opt', function()
local s = assert(spec.parse({ 'owner/repo', tag = 'v1', opt = true }))
t.falsy(make({ tag = 'v1', opt = false }):is_current(s))
end)
t.it('is true when the tracked branch still matches', function()
local s = assert(spec.parse({ 'owner/repo', branch = 'main' }))
t.truthy(make({ branch = 'main', opt = false }):is_current(s))
end)
t.it('is false when the spec switched to another branch', function()
local s = assert(spec.parse({ 'owner/repo', branch = 'master' }))
t.falsy(make({ branch = 'main', opt = false }):is_current(s))
end)
t.it('is false when a spec swaps between a tag and a branch', function()
local tagged = assert(spec.parse({ 'owner/repo', tag = 'v1' }))
local branched = assert(spec.parse({ 'owner/repo', branch = 'main' }))
t.falsy(make({ branch = 'main', opt = false }):is_current(tagged))
t.falsy(make({ tag = 'v1', opt = false }):is_current(branched))
end)
end)
t.describe('lock.needs_install', function()
local function make(entry)
local lock = Lock.new(tmpdir() .. '/lock.json')
if entry then
lock:set('repo', entry)
end
return lock
end
local tagged = assert(spec.parse({ 'owner/repo', tag = 'v1' }))
local branched = assert(spec.parse({ 'owner/repo', branch = 'main' }))
t.it('installs anything that is missing from the lockfile', function()
t.truthy(make(nil):needs_install(tagged, { present = false }))
t.truthy(make(nil):needs_install(branched, { present = false }))
end)
t.it('installs anything whose directory is gone', function()
t.truthy(make({ tag = 'v1' }):needs_install(tagged, { present = false }))
t.truthy(make({ branch = 'main' }):needs_install(branched, { present = false }))
end)
t.it('skips an installed tag, which cannot have moved', function()
t.falsy(make({ tag = 'v1' }):needs_install(tagged, { present = true }))
t.falsy(make({ tag = 'v1' }):needs_install(tagged, { present = true, update = true }))
end)
t.it('leaves an installed branch alone on a plain sync', function()
t.falsy(make({ branch = 'main' }):needs_install(branched, { present = true }))
end)
t.it('refetches an installed branch on an update run', function()
t.truthy(make({ branch = 'main' }):needs_install(branched, { present = true, update = true }))
end)
t.it('refetches everything when forced', function()
t.truthy(make({ tag = 'v1' }):needs_install(tagged, { present = true, force = true }))
t.truthy(make({ branch = 'main' }):needs_install(branched, { present = true, force = true }))
end)
t.it('installs a branch whose name changed even without an update run', function()
local other = assert(spec.parse({ 'owner/repo', branch = 'master' }))
t.truthy(make({ branch = 'main' }):needs_install(other, { present = true }))
end)
end)