local t = require('spec.zync_test')
local function reload_zync()
package.loaded['zync'] = nil
package.loaded['zync.api'] = nil
package.loaded['zync.api.commands.commands'] = nil
package.loaded['zync.api.commands.Command'] = nil
package.loaded['zync.api.configurator'] = nil
package.loaded['zync.api.configurator.sandbox'] = nil
package.loaded['argparse'] = nil
return require('zync')
end
local function stub_manager_handlers()
local manager = require('zync.api.manager')
local calls = {}
local stubs = {}
local handlers = {
init = 'init',
conf = 'configure',
build = 'build',
flash = 'flash',
run = 'run',
clean = 'clean',
boards = 'boards',
runners = 'runners',
}
for command, handler in pairs(handlers) do
calls[command] = {}
stubs[handler] = t.stub(manager, handler, function(config)
table.insert(calls[command], config)
end)
end
return stubs, calls
end
local function stub_configurator_config(config)
local configurator = require('zync.api.configurator')
return t.stub(configurator, 'init_conf', function()
return config
end)
end
local function stub_lfs_symlinkattributes(value)
return t.stub(t.lfs, 'symlinkattributes', function(_)
return value
end)
end
local function stub_lfs_attributes(value)
return t.stub(t.lfs, 'attributes', function(_)
return value
end)
end
local function revert_stubs(stubs)
for _, stub in pairs(stubs) do
stub:revert()
end
end
local function get_upvalue(fn, name)
local i = 1
while true do
local up_name, up_val = debug.getupvalue(fn, i)
if not up_name then
return nil
end
if up_name == name then
return up_val
end
i = i + 1
end
end
describe('zync top-level API', function()
local print_stub
before_each(function()
t.helpers.setup()
print_stub = t.stub(_G, 'print')
end)
after_each(function()
print_stub:revert()
t.helpers.teardown()
end)
it('loads zync without crashing', function()
local z = reload_zync()
t.assert.is_truthy(z)
t.assert.is_table(z.api)
end)
it('delegates zync.run to api.run', function()
local z = reload_zync()
local run_stub = t.stub(z.api, 'run', function() end)
z.run()
t.assert.stub(run_stub).was.called()
run_stub:revert()
end)
it('prints version and exits when requested', function()
local z = reload_zync()
local og_args = z.api.args
z.api.args = { version = true }
t.exit_handler = function()
error('exit')
end
local ok, err = pcall(function()
z.api.version()
end)
t.assert.is_false(ok)
t.assert.is_truthy(err:match('exit'))
t.assert.stub(print_stub).was_called_with(_G.zync.CMD .. ' v' .. _G.zync.VERSION)
z.api.args = og_args
end)
it('runs default build command when no command is selected', function()
local stubs, calls = stub_manager_handlers()
local z = reload_zync()
z.api.args = {}
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equal(1, #calls.build)
revert_stubs(stubs)
end)
it('dispatches command and merges cli args into config', function()
t.helpers.write_file('zconf.lua', "return { board = 'conf_board', runner = 'conf_runner' }")
local stubs, calls = stub_manager_handlers()
local z = reload_zync()
z.api.args = { build = true, board = 'cli_board' }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equal(1, #calls.build)
t.assert.equal('cli_board', calls.build[1].board)
t.assert.equal('conf_runner', calls.build[1].runner)
t.assert.equal(t.tmp, calls.build[1].cwd)
revert_stubs(stubs)
end)
it('parses global profile option and applies it to config', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
local og_arg = _G.arg
_G.arg = { '--profile', 'sim' }
local z = reload_zync()
t.assert.equals('sim_board', z.api.config.board)
t.assert.equals('sim_runner', z.api.config.runner)
t.assert.equals('sim', z.api.config.profile)
_G.arg = og_arg
end)
it('ignores --profile when --all is provided', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
local og_arg = _G.arg
_G.arg = { '--all', '--profile', 'sim' }
local z = reload_zync()
t.assert.equals('prod', z.api.config.profile)
_G.arg = og_arg
end)
it('runs profile-aware commands for all profiles when --all is set', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'sim_board', runner = 'sim_runner' },
prod = { board = 'prod_board', runner = 'prod_runner' },
},
default_profile = 'sim',
}
]]
)
local stubs, calls = stub_manager_handlers()
local z = reload_zync()
z.api.args = { build = true, all = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equal(2, #calls.build)
t.assert.equals('sim', calls.build[1].profile)
t.assert.equals('prod', calls.build[2].profile)
revert_stubs(stubs)
end)
it('returns nil for helper profile resolution when config missing', function()
local z = reload_zync()
local ensure = get_upvalue(z.api.run, 'ensure_compile_commands_link')
t.assert.equals('function', type(ensure))
local resolve_default = get_upvalue(ensure, 'resolve_default_profile')
t.assert.equals('function', type(resolve_default))
t.assert.is_nil(resolve_default(nil))
t.assert.is_nil(resolve_default({}))
local resolve_profiles = get_upvalue(z.api.run, 'resolve_profiles')
t.assert.equals('function', type(resolve_profiles))
t.assert.is_nil(resolve_profiles(nil))
t.assert.is_nil(resolve_profiles({}))
end)
it('skips compile_commands link when config is invalid', function()
local stubs = stub_manager_handlers()
local z = reload_zync()
z.api.config = {
invalid = true,
profiles = { prod = {} },
project_dir = t.tmp,
}
local called = false
local link_stub = t.stub(t.lfs, 'symlinkattributes', function(_)
called = true
return nil
end)
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_false(called)
link_stub:revert()
revert_stubs(stubs)
end)
it('uses ln -sf fallback when lfs.link is unavailable', function()
local stubs = stub_manager_handlers()
local z = reload_zync()
z.api.config = {
profiles = { prod = {} },
default_profile = 'prod',
project_dir = t.tmp,
}
local exec_calls = {}
local exec_stub = t.stub(_G.os, 'execute', function(cmd)
table.insert(exec_calls, cmd)
return true
end)
local link_stub = t.stub(t.lfs, 'symlinkattributes', function(_)
return nil
end)
local og_link = t.lfs.link
t.lfs.link = nil
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local target = t.tmp .. '/build/prod/compile_commands.json'
local link_path = t.tmp .. '/build/compile_commands.json'
local expected = 'ln -sf ' .. target .. ' ' .. link_path
local has_ln = false
for _, call in ipairs(exec_calls) do
if call == expected then
has_ln = true
end
end
t.assert.is_true(has_ln)
t.lfs.link = og_link
link_stub:revert()
exec_stub:revert()
revert_stubs(stubs)
end)
it('skips compile_commands link when default profile cannot be resolved', function()
local stubs = stub_manager_handlers()
local z = reload_zync()
z.api.config = {
profiles = {},
project_dir = t.tmp,
}
local exec_calls = {}
local exec_stub = t.stub(_G.os, 'execute', function(cmd)
table.insert(exec_calls, cmd)
return true
end)
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equals(0, #exec_calls)
exec_stub:revert()
revert_stubs(stubs)
end)
it('runs --all profiles in alphabetical order when profile_order missing', function()
local manager = require('zync.api.manager')
local build_calls = {}
local build_stub = t.stub(manager, 'build', function(config)
table.insert(build_calls, config.profile)
return 0
end)
local z = reload_zync()
z.api.config = {
profiles = { zeta = {}, alpha = {} },
project_dir = t.tmp,
}
local configurator = require('zync.api.configurator')
local init_stub = t.stub(configurator, 'init_conf', function(_, profile)
return { profile = profile, profiles = z.api.config.profiles }
end)
local link_stub = t.stub(t.lfs, 'symlinkattributes', function(_)
return { mode = 'link' }
end)
z.api.args = { build = true, all = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equals('alpha', build_calls[1])
t.assert.equals('zeta', build_calls[2])
link_stub:revert()
init_stub:revert()
build_stub:revert()
end)
it('exits early when a --all profile run fails', function()
local manager = require('zync.api.manager')
local build_calls = {}
local build_stub = t.stub(manager, 'build', function(config)
table.insert(build_calls, config.profile)
return 2
end)
local z = reload_zync()
z.api.config = {
profiles = { sim = {}, prod = {} },
profile_order = { 'sim', 'prod' },
project_dir = t.tmp,
}
local configurator = require('zync.api.configurator')
local init_stub = t.stub(configurator, 'init_conf', function(_, profile)
return { profile = profile, profiles = z.api.config.profiles }
end)
local link_stub = t.stub(t.lfs, 'symlinkattributes', function(_)
return { mode = 'link' }
end)
z.api.args = { build = true, all = true }
t.exit_handler = function(code)
error(code)
end
local ok, err = pcall(function()
z.api.run()
end)
t.assert.is_false(ok)
t.assert.equals('2', tostring(err))
t.assert.equals(1, #build_calls)
t.assert.equals('sim', build_calls[1])
link_stub:revert()
init_stub:revert()
build_stub:revert()
end)
it('logs active profile after command log when selected', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
local og_arg = _G.arg
_G.arg = { '--profile', 'sim' }
_G.ZYNC_LOGS = true
local stubs = stub_manager_handlers()
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local calls = print_stub.calls
local function find_call(msg)
for i, call in ipairs(calls) do
if call.vals and call.vals[1] == msg then
return i
end
end
end
local cmd_idx = find_call("zync: Running command 'build'")
if not cmd_idx then
cmd_idx = find_call("zync: No command specified, defaulting to 'build'")
end
local prof_idx = find_call("zync: Using profile 'sim'")
t.assert.is_truthy(prof_idx)
if cmd_idx then
t.assert.is_true(cmd_idx < prof_idx)
end
revert_stubs(stubs)
_G.arg = og_arg
end)
it('logs when profiles are not configured after command log', function()
t.helpers.write_file('zconf.lua', "return { board = 'conf_board', runner = 'conf_runner' }")
local og_arg = _G.arg
_G.arg = {}
_G.ZYNC_LOGS = true
local stubs = stub_manager_handlers()
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local calls = print_stub.calls
local function find_call(msg)
for i, call in ipairs(calls) do
if call.vals and call.vals[1] == msg then
return i
end
end
end
local cmd_idx = find_call("zync: Running command 'build'")
if not cmd_idx then
cmd_idx = find_call("zync: No command specified, defaulting to 'build'")
end
local prof_idx = find_call('zync: Profiles not configured - running in profile-less mode')
t.assert.is_truthy(prof_idx)
if cmd_idx then
t.assert.is_true(cmd_idx < prof_idx)
end
revert_stubs(stubs)
_G.arg = og_arg
end)
it('does not log profile status for init command', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'prod',
}
]]
)
local og_arg = _G.arg
_G.arg = { '--profile', 'prod' }
_G.ZYNC_LOGS = true
local stubs = stub_manager_handlers()
local z = reload_zync()
z.api.args = { init = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local calls = print_stub.calls
local function has_call(msg)
for _, call in ipairs(calls) do
if call.vals and call.vals[1] == msg then
return true
end
end
return false
end
t.assert.is_false(has_call("zync: Using profile 'prod'"))
t.assert.is_false(has_call('zync: Profiles not configured - running in profile-less mode'))
revert_stubs(stubs)
_G.arg = og_arg
end)
it('creates compile_commands.json symlink for default profile', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
os.execute('mkdir -p build/prod')
t.helpers.write_file('build/prod/compile_commands.json', '[]')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local link_attr = t.lfs.symlinkattributes('build/compile_commands.json')
t.assert.is_truthy(link_attr)
t.assert.equal('link', link_attr.mode)
t.assert.equal('[]', t.helpers.read_file('build/compile_commands.json'))
build_stub:revert()
end)
it('keeps existing compile_commands.json symlink intact', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
os.execute('mkdir -p build/prod')
t.helpers.write_file('build/prod/compile_commands.json', '[1]')
os.execute('ln -s prod/compile_commands.json build/compile_commands.json')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local link_attr = t.lfs.symlinkattributes('build/compile_commands.json')
t.assert.is_truthy(link_attr)
t.assert.equal('link', link_attr.mode)
t.assert.equal('[1]', t.helpers.read_file('build/compile_commands.json'))
build_stub:revert()
end)
it('creates compile_commands.json link for first declared profile when default_profile missing', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'sim_board', runner = 'qemu' },
prod = { board = 'prod_board', runner = 'openocd' },
},
}
]]
)
os.execute('mkdir -p build/sim build/prod')
t.helpers.write_file('build/sim/compile_commands.json', '[sim]')
t.helpers.write_file('build/prod/compile_commands.json', '[prod]')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local link_attr = t.lfs.symlinkattributes('build/compile_commands.json')
t.assert.is_truthy(link_attr)
t.assert.equal('link', link_attr.mode)
t.assert.equal('[sim]', t.helpers.read_file('build/compile_commands.json'))
build_stub:revert()
end)
it('replaces existing compile_commands.json file with symlink', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'prod',
}
]]
)
os.execute('mkdir -p build/prod')
t.helpers.write_file('build/prod/compile_commands.json', '[prod]')
t.helpers.write_file('build/compile_commands.json', '[stale]')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local link_attr = t.lfs.symlinkattributes('build/compile_commands.json')
t.assert.is_truthy(link_attr)
t.assert.equal('link', link_attr.mode)
t.assert.equal('[prod]', t.helpers.read_file('build/compile_commands.json'))
build_stub:revert()
end)
it('does not create compile_commands.json symlink when profiles are not configured', function()
t.helpers.write_file('zconf.lua', "return { board = 'conf_board', runner = 'conf_runner' }")
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_nil(t.lfs.symlinkattributes('build/compile_commands.json'))
build_stub:revert()
end)
it('does not create compile_commands.json symlink for excluded commands', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'prod',
}
]]
)
local manager = require('zync.api.manager')
local stubs = {
init = t.stub(manager, 'init', function(_) end),
boards = t.stub(manager, 'boards', function(_) end),
runners = t.stub(manager, 'runners', function(_) end),
profiles = t.stub(manager, 'profiles', function(_) end),
}
local cases = {
{ name = 'init', args = { init = true } },
{ name = 'boards', args = { boards = true } },
{ name = 'runners', args = { runners = true } },
{ name = 'profiles', args = { profiles = true } },
}
for _, case in ipairs(cases) do
local z = reload_zync()
z.api.args = case.args
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_nil(t.lfs.symlinkattributes('build/compile_commands.json'))
end
for _, stub in pairs(stubs) do
stub:revert()
end
end)
it('skips compile_commands link when config is invalid', function()
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
t.helpers.write_file('zconf.lua', "return { board = os.getenv('ZEPHYR_BASE') }")
local ok, err = pcall(function()
reload_zync()
end)
t.assert.is_false(ok)
t.assert.is_truthy(tostring(err):match("access to global 'os' is not allowed"))
t.assert.is_nil(t.lfs.symlinkattributes('build/compile_commands.json'))
build_stub:revert()
end)
it('skips compile_commands link when no profiles are configured', function()
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
t.helpers.write_file('zconf.lua', "return { board = 'conf_board', runner = 'conf_runner' }")
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_nil(t.lfs.symlinkattributes('build/compile_commands.json'))
build_stub:revert()
end)
it('falls back to alphabetical profile when no profile_order available', function()
os.execute('rm -rf build')
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
zeta = { board = 'zeta_board', runner = 'openocd' },
alpha = { board = 'alpha_board', runner = 'openocd' },
},
}
]]
)
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local link_calls = {}
local link_stub = t.stub(t.lfs, 'link', function(target, link_path, _)
table.insert(link_calls, { target = target, link_path = link_path })
return true
end)
local z = reload_zync()
z.api.config.profile_order = {}
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_true(#link_calls > 0)
t.assert.is_truthy(link_calls[1].target:match('build/alpha/compile_commands.json$'))
build_stub:revert()
link_stub:revert()
end)
it('falls back to available profile when profile_order is invalid', function()
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'conf_board', runner = 'conf_runner' },
},
}
]]
)
os.execute('mkdir -p build/sim')
t.helpers.write_file('build/sim/compile_commands.json', '[sim]')
local z = reload_zync()
z.api.config.profile_order = { 'missing' }
z.api.config.default_profile = ''
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equal('[sim]', t.helpers.read_file('build/compile_commands.json'))
build_stub:revert()
end)
it('returns early when compile_commands path is a non-link non-file', function()
os.execute('rm -rf build')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'sim',
}
]]
)
local link_stub = stub_lfs_symlinkattributes({ mode = 'directory' })
local link_call_stub = t.stub(t.lfs, 'link', function(_, _, _)
return true
end)
local os_execute_stub = t.stub(_G.os, 'execute')
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.stub(link_call_stub).was_not.called()
link_stub:revert()
link_call_stub:revert()
os_execute_stub:revert()
build_stub:revert()
end)
it('uses lfs.attributes branch when symlinkattributes is unavailable', function()
os.execute('rm -rf build')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'sim',
}
]]
)
local attr_stub = stub_lfs_attributes({ mode = 'file' })
local og_symlinkattributes = t.lfs.symlinkattributes
t.lfs.symlinkattributes = nil
local link_call_stub = t.stub(t.lfs, 'link', function(_, _, _)
return true
end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.stub(link_call_stub).was_not.called()
build_stub:revert()
if attr_stub then
attr_stub:revert()
end
t.lfs.symlinkattributes = og_symlinkattributes
link_call_stub:revert()
end)
it('falls back to ln -sf when lfs.link fails', function()
os.execute('rm -rf build')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'sim',
}
]]
)
os.execute('mkdir -p build/sim')
t.helpers.write_file('build/sim/compile_commands.json', '[sim]')
local link_stub = t.stub(t.lfs, 'link', function(_, _, _)
return false
end)
local os_execute_stub = t.stub(_G.os, 'execute')
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
local found_ln = false
for _, call in ipairs(os_execute_stub.calls) do
if
call.vals
and type(call.vals[1]) == 'string'
and call.vals[1]:match('^ln %-sf ')
and call.vals[1]:match('compile_commands%.json')
then
found_ln = true
break
end
end
t.assert.is_true(found_ln)
os_execute_stub:revert()
link_stub:revert()
build_stub:revert()
end)
it('uses cwd when project_dir is missing for compile_commands link creation', function()
os.execute('rm -rf build')
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
sim = { board = 'conf_board', runner = 'conf_runner' },
},
default_profile = 'sim',
}
]]
)
os.execute('mkdir -p build/sim')
t.helpers.write_file('build/sim/compile_commands.json', '[sim]')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local link_calls = {}
local link_stub = t.stub(t.lfs, 'link', function(target, link_path, _)
table.insert(link_calls, { target = target, link_path = link_path })
return true
end)
local z = reload_zync()
z.api.config.project_dir = ''
z.api.args = { build = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_true(#link_calls > 0)
t.assert.is_truthy(link_calls[1].target:match(t.tmp .. '/build/sim/compile_commands.json$'))
build_stub:revert()
link_stub:revert()
end)
it('removes build dir for clean command in profile-less projects', function()
t.helpers.write_file('zconf.lua', "return { board = 'conf_board', runner = 'conf_runner' }")
os.execute('mkdir -p build/subdir')
t.helpers.write_file('build/subdir/marker.txt', 'keep')
local z = reload_zync()
z.api.args = { clean = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.helpers.dir_exists('build', false)
end)
it('removes build dir for clean command in profile projects', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
os.execute('mkdir -p build/prod build/sim')
t.helpers.write_file('build/prod/marker.txt', 'prod')
t.helpers.write_file('build/sim/marker.txt', 'sim')
local z = reload_zync()
z.api.args = { clean = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.helpers.dir_exists('build', false)
end)
it('keeps other profile build dirs intact when pre-clean is requested', function()
t.helpers.write_file(
'zconf.lua',
[[
return {
profiles = {
prod = { board = 'conf_board', runner = 'conf_runner' },
sim = { board = 'sim_board', runner = 'sim_runner' },
},
default_profile = 'prod',
}
]]
)
os.execute('mkdir -p build/prod build/sim')
t.helpers.write_file('build/prod/marker.txt', 'prod')
t.helpers.write_file('build/sim/marker.txt', 'sim')
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_) end)
local z = reload_zync()
z.api.args = { build = true, pre_clean = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.is_nil(t.lfs.attributes('build/prod/marker.txt'))
t.assert.is_truthy(t.lfs.attributes('build/sim/marker.txt'))
build_stub:revert()
end)
it('dispatches all commands with parsed args', function()
t.helpers.write_file('zconf.lua', "return { board = 'conf_board', runner = 'conf_runner' }")
local stubs, calls = stub_manager_handlers()
local cases = {
{ name = 'init', args = { init = true } },
{ name = 'conf', args = { conf = true, board = 'cli_board' } },
{ name = 'clean', args = { clean = true } },
{ name = 'build', args = { build = true, board = 'cli_board', runner = 'cli_runner' } },
{
name = 'flash',
args = { flash = true, board = 'cli_board', runner = 'cli_runner', image = 2, list = true },
},
{ name = 'run', args = { run = true, image = 1 } },
{ name = 'boards', args = { boards = true } },
{ name = 'runners', args = { runners = true } },
}
for _, case in ipairs(cases) do
local z = reload_zync()
z.api.args = case.args
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.assert.equal(1, #calls[case.name])
if case.args.board then
t.assert.equal(case.args.board, calls[case.name][1].board)
elseif case.name ~= 'boards' and case.name ~= 'runners' then
t.assert.equal('conf_board', calls[case.name][1].board)
end
if case.args.runner then
t.assert.equal(case.args.runner, calls[case.name][1].runner)
elseif case.name ~= 'boards' and case.name ~= 'runners' then
t.assert.equal('conf_runner', calls[case.name][1].runner)
end
if case.args.image then
t.assert.equal(tonumber(case.args.image), calls[case.name][1].image)
end
if case.args.list then
t.assert.is_true(calls[case.name][1].list)
end
calls[case.name] = {}
end
revert_stubs(stubs)
end)
it('exits with handler exit code', function()
local manager = require('zync.api.manager')
local build_stub = t.stub(manager, 'build', function(_)
return 7
end)
local z = reload_zync()
z.api.args = { build = true }
t.exit_handler = function(code)
error('exit:' .. tostring(code))
end
local ok, err = pcall(function()
z.api.run()
end)
t.assert.is_false(ok)
t.assert.is_truthy(err:match('exit:7'))
build_stub:revert()
end)
it('runs clean before selected command when requested', function()
local manager = require('zync.api.manager')
local order = {}
local clean_stub = t.stub(manager, 'clean', function(_)
table.insert(order, 'clean')
end)
local build_stub = t.stub(manager, 'build', function(_)
table.insert(order, 'build')
end)
local z = reload_zync()
z.api.args = { build = true, pre_clean = true }
t.exit_handler = function()
error('exit')
end
pcall(function()
z.api.run()
end)
t.helpers.assert_tables_equal(order, { 'clean', 'build' })
clean_stub:revert()
build_stub:revert()
end)
it('exits when pre-clean fails', function()
local manager = require('zync.api.manager')
local clean_stub = t.stub(manager, 'clean', function(_)
return 5
end)
local build_stub = t.stub(manager, 'build', function(_)
return 0
end)
local z = reload_zync()
z.api.args = { build = true, pre_clean = true }
t.exit_handler = function(code)
error('exit:' .. tostring(code))
end
local ok, err = pcall(function()
z.api.run()
end)
t.assert.is_false(ok)
t.assert.is_truthy(err:match('exit:5'))
clean_stub:revert()
build_stub:revert()
end)
end)