summaryrefslogtreecommitdiff
path: root/tests/actions/addon/test.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tests/actions/addon/test.lua')
-rw-r--r--tests/actions/addon/test.lua334
1 files changed, 308 insertions, 26 deletions
diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua
index d393c0545..2be6acf94 100644
--- a/tests/actions/addon/test.lua
+++ b/tests/actions/addon/test.lua
@@ -1,8 +1,17 @@
import("core.base.global")
--- write a minimal plugin that prints its name when run
+-- the payloads of the mocked addons
+--
+-- the plugins and templates are not namespaced, so they are named with the addon name to keep them unique,
+-- the other payloads are always referenced with `@addon/<addon>/` or `@self/`, so they can use fixed names
+local RULENAME = "flash"
+local RULEBASENAME = "base"
+local TOOLCHAINAME = "xtensa"
+local MODULENAME = "sdkconfig"
+local INCLUDESNAME = "check"
+
+-- write a minimal plugin, it imports a module of its own addon with `@self`
--
--- the plugins of an addon are placed in its `plugins` payload directory,
-- e.g. <addondir>/plugins/<name>/xmake.lua
function _write_plugin(dir, name)
io.writefile(path.join(dir, "xmake.lua"), string.format([[
@@ -11,11 +20,15 @@ task("%s")
on_run("main")
set_menu {usage = "xmake %s", description = "say hello from %s"}
]], name, name, name))
- io.writefile(path.join(dir, "main.lua"), string.format([[function main() print("%s") end]], name))
+ io.writefile(path.join(dir, "main.lua"), string.format([[
+function main()
+ import("@self.%s")
+ print("%s: " .. %s())
+end
+]], MODULENAME, name, MODULENAME))
end
--- write a minimal template into the `templates` payload directory of an addon,
--- e.g. <addondir>/templates/<language>/<templateid>/xmake.lua
+-- write a minimal template, e.g. <addondir>/templates/<language>/<templateid>/xmake.lua
function _write_template(dir, lang, templateid)
local templatedir = path.join(dir, "templates", lang, templateid)
io.writefile(path.join(templatedir, "xmake.lua"), [[
@@ -28,37 +41,106 @@ int main(int argc, char** argv) { return 0; }
]])
end
--- write an addon payload directory, it provides a plugin and a template
+-- write two rules, the main one depends on the other one of the same addon with `@self`
+--
+-- e.g. <addondir>/rules/<name>/xmake.lua
+function _write_rules(dir, name)
+ io.writefile(path.join(dir, "rules", RULEBASENAME, "xmake.lua"), string.format([[
+rule("%s")
+ on_load(function (target)
+ print("hello from rule %s")
+ end)
+]], RULEBASENAME, RULEBASENAME))
+ io.writefile(path.join(dir, "rules", RULENAME, "xmake.lua"), string.format([[
+rule("%s")
+ add_deps("@self/%s")
+ on_load(function (target)
+ import("@self.%s")
+ print("hello from rule %s of %s: " .. %s())
+ end)
+]], RULENAME, RULEBASENAME, MODULENAME, RULENAME, name, MODULENAME))
+end
+
+-- write a minimal toolchain, e.g. <addondir>/toolchains/<name>/xmake.lua
+function _write_toolchain(dir, name)
+ io.writefile(path.join(dir, "toolchains", TOOLCHAINAME, "xmake.lua"), string.format([[
+toolchain("%s")
+ set_kind("standalone")
+ set_description("hello from toolchain %s of %s")
+ on_load(function (toolchain)
+ toolchain:set("toolset", "cc", "gcc")
+ end)
+]], TOOLCHAINAME, TOOLCHAINAME, name))
+end
+
+-- write a minimal module, e.g. <addondir>/modules/<name>.lua
+function _write_module(dir, name)
+ io.writefile(path.join(dir, "modules", MODULENAME .. ".lua"), string.format([[
+function main()
+ return "hello from module %s of %s"
+end
+]], MODULENAME, name))
+end
+
+-- write a minimal includes file, e.g. <addondir>/includes/<name>/xmake.lua
+function _write_includes(dir, name)
+ io.writefile(path.join(dir, "includes", INCLUDESNAME, "xmake.lua"), string.format([[
+print("hello from includes %s of %s")
+]], INCLUDESNAME, name))
+end
+
+-- write an addon payload directory, it provides all the supported payloads
function _write_addon(dir, name)
_write_plugin(path.join(dir, "plugins", name), name)
_write_template(dir, "c", name)
+ _write_rules(dir, name)
+ _write_toolchain(dir, name)
+ _write_module(dir, name)
+ _write_includes(dir, name)
end
-- write an addon package description, its payloads are placed in the `src` directory
--
-- addons in a repository are described as packages, e.g. <repodir>/addons/<first-letter>/<name>/xmake.lua
-function _write_addon_package(dir, name)
+--
+-- @param opt the options, e.g. {deps = {"other-addon"}}
+function _write_addon_package(dir, name, opt)
+ opt = opt or {}
+ local deps = ""
+ for _, depname in ipairs(opt.deps) do
+ deps = deps .. string.format("\n add_deps(\"%s\", {kind = \"addon\"})", depname)
+ end
io.writefile(path.join(dir, "xmake.lua"), string.format([[
package("%s")
set_kind("addon")
set_description("say hello from %s")
- set_sourcedir(path.join(os.scriptdir(), "src"))
-]], name, name))
+ set_sourcedir(path.join(os.scriptdir(), "src"))%s
+]], name, name, deps))
_write_addon(path.join(dir, "src"), name)
end
-- create a temporary addon repository (packages layout: addons/<first-letter>/<name>) and register it
--
+-- @param basenames the addon base names, e.g. {"hello", "world"}
+-- @param opt the options, e.g. {deps = {hello = {2}}}, the first addon depends on the second one
+--
-- @return reponame, names, cleanup
-function _mock_repo(basenames)
+function _mock_repo(basenames, opt)
+ opt = opt or {}
local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "")
local reponame = "addon-test-repo-" .. suffix
local repodir = os.tmpfile() .. ".addon-repo"
local names = {}
for _, base in ipairs(basenames) do
- local name = base .. "-" .. suffix
- _write_addon_package(path.join(repodir, "addons", name:sub(1, 1), name), name)
- table.insert(names, name)
+ table.insert(names, base .. "-" .. suffix)
+ end
+ for idx, base in ipairs(basenames) do
+ local name = names[idx]
+ local deps = {}
+ for _, depidx in ipairs(table.wrap((opt.deps or {})[base])) do
+ table.insert(deps, names[depidx])
+ end
+ _write_addon_package(path.join(repodir, "addons", name:sub(1, 1), name), name, {deps = deps})
end
-- register the repository into the cache
@@ -73,8 +155,8 @@ function _mock_repo(basenames)
local function cleanup()
for _, name in ipairs(names) do
+ try { function () os.runv("xmake", {"addon", "--remove", "--force", name}) end }
os.tryrm(path.join(global.directory(), "addons", name))
- try { function () os.runv("xmake", {"addon", "--remove", name}) end }
end
local cache = os.isfile(cachefile) and io.load(cachefile) or {}
if cache.repositories then
@@ -88,8 +170,8 @@ function _mock_repo(basenames)
end
-- run the given function with a mocked repository, we always clean it up even if the test fails
-function _with_repo(basenames, func)
- local reponame, names, cleanup = _mock_repo(basenames)
+function _with_repo(basenames, func, opt)
+ local reponame, names, cleanup = _mock_repo(basenames, opt)
try
{
function ()
@@ -102,6 +184,38 @@ function _with_repo(basenames, func)
}
end
+-- run `xmake config` in a temporary project and return its output
+function _config_project(content)
+ local projectdir = os.tmpfile() .. ".addon-project"
+ os.tryrm(projectdir)
+ io.writefile(path.join(projectdir, "xmake.lua"), content)
+ local oldir = os.cd(projectdir)
+ local out, errors
+ try
+ {
+ function ()
+ out = os.iorunv("xmake", {"config", "-y"})
+ end,
+ catch
+ {
+ function (e)
+ errors = e
+ end
+ },
+ finally
+ {
+ function ()
+ os.cd(oldir)
+ os.tryrm(projectdir)
+ end
+ }
+ }
+ if errors then
+ raise(errors)
+ end
+ return out
+end
+
-- install an addon from a repository, by plain name and by repo@name
function test_install_from_repo(t)
_with_repo({"hello"}, function (reponame, names)
@@ -120,6 +234,29 @@ function test_install_from_repo(t)
end)
end
+-- an addon can reference its own payloads with `@self`, it never needs to know its installed name
+function test_self_reference(t)
+ _with_repo({"hello"}, function (_, names)
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ -- the plugin imports its own module with `import("@self.<module>")`
+ local out = os.iorunv("xmake", {name})
+ t:require(out:find("hello from module " .. MODULENAME .. " of " .. name, 1, true))
+
+ -- the rule depends on the other rule of the same addon with `add_deps("@self/<rule>")`
+ out = _config_project(string.format([[
+target("test")
+ set_kind("phony")
+ add_rules("@addon/%s/%s")
+]], name, RULENAME))
+ t:require(out:find("hello from rule " .. RULEBASENAME, 1, true))
+ t:require(out:find("hello from rule " .. RULENAME .. " of " .. name, 1, true))
+
+ os.runv("xmake", {"addon", "--remove", name})
+ end)
+end
+
-- the templates of an installed addon can be used by `xmake create`
function test_install_templates(t)
_with_repo({"hello"}, function (_, names)
@@ -136,8 +273,135 @@ function test_install_templates(t)
os.runv("xmake", {"create", "-l", "c", "-t", name, "-P", projectdir})
t:require(os.isfile(path.join(projectdir, "xmake.lua")))
t:require(os.isfile(path.join(projectdir, "src", "main.c")))
-
os.tryrm(projectdir)
+
+ os.runv("xmake", {"addon", "--remove", name})
+ end)
+end
+
+-- the rules of an installed addon can be used with the `@addon/<addon>/` prefix
+function test_install_rules(t)
+ _with_repo({"hello"}, function (_, names)
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ -- it should be found in the global rules
+ local script = string.format("import(\"core.project.rule\"); print(rule.rule(\"@addon/%s/%s\") ~= nil)", name, RULENAME)
+ t:require(os.iorunv("xmake", {"lua", "-c", script}):find("true", 1, true))
+
+ -- the addon name is always required
+ local script2 = string.format("import(\"core.project.rule\"); print(rule.rule(\"@addon/%s\"))", RULENAME)
+ t:require_not(try { function () os.iorunv("xmake", {"lua", "-c", script2}); return true end })
+
+ os.runv("xmake", {"addon", "--remove", name})
+ end)
+end
+
+-- the includes of an installed addon can be used with the `@addon/<addon>/` prefix
+function test_install_includes(t)
+ _with_repo({"hello"}, function (_, names)
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ local out = _config_project(string.format([[
+includes("@addon/%s/%s")
+target("test")
+ set_kind("phony")
+]], name, INCLUDESNAME))
+ t:require(out:find("hello from includes " .. INCLUDESNAME .. " of " .. name, 1, true))
+
+ os.runv("xmake", {"addon", "--remove", name})
+ end)
+end
+
+-- the toolchains of an installed addon can be loaded with the `@addon/<addon>/` prefix
+function test_install_toolchains(t)
+ _with_repo({"hello"}, function (_, names)
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ local script = string.format("import(\"core.tool.toolchain\"); print(toolchain.load(\"@addon/%s/%s\"):get(\"description\"))", name, TOOLCHAINAME)
+ t:require(os.iorunv("xmake", {"lua", "-c", script}):find("hello from toolchain " .. TOOLCHAINAME .. " of " .. name, 1, true))
+
+ -- it can also be bound to a package, e.g. "@addon/<addon>/clang@llvm"
+ local script2 = string.format("import(\"core.tool.toolchain\"); print(toolchain.load(\"@addon/%s/%s@llvm\"):config(\"packages\"))", name, TOOLCHAINAME)
+ t:require(os.iorunv("xmake", {"lua", "-c", script2}):find("llvm", 1, true))
+
+ -- it should not be found without the `@addon/<addon>/` prefix
+ local script3 = string.format("import(\"core.tool.toolchain\"); print(toolchain.load(\"%s\"))", TOOLCHAINAME)
+ t:require_not(try { function () os.iorunv("xmake", {"lua", "-c", script3}); return true end })
+
+ os.runv("xmake", {"addon", "--remove", name})
+ end)
+end
+
+-- the modules of an installed addon can be imported with the `@addon.<addon>.` prefix
+function test_install_modules(t)
+ _with_repo({"hello"}, function (_, names)
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ local script = string.format("import(\"@addon.%s.%s\"); print(%s())", name, MODULENAME, MODULENAME)
+ t:require(os.iorunv("xmake", {"lua", "-c", script}):find("hello from module " .. MODULENAME .. " of " .. name, 1, true))
+
+ -- the addon name is always required
+ local script2 = string.format("import(\"@addon.%s\")", MODULENAME)
+ t:require_not(try { function () os.iorunv("xmake", {"lua", "-c", script2}); return true end })
+
+ os.runv("xmake", {"addon", "--remove", name})
+ end)
+end
+
+-- an addon can depend on the other addons with `add_deps(name, {kind = "addon"})`
+function test_addon_deps(t)
+ _with_repo({"hello", "world"}, function (_, names)
+ local name, depname = names[1], names[2]
+
+ -- installing the first addon should install and activate its addon dependency
+ os.runv("xmake", {"addon", "--install", "-y", name})
+ t:require(os.iorunv("xmake", {"addon", "--list"}):find(depname, 1, true))
+
+ -- the payloads of the dependency should be usable, e.g. its plugin
+ t:require(os.iorunv("xmake", {depname}):find(depname, 1, true))
+
+ -- we cannot remove the dependency, it's depended on by the other addon
+ t:require_not(try { function () os.runv("xmake", {"addon", "--remove", depname}); return true end })
+
+ os.runv("xmake", {"addon", "--remove", name})
+ os.runv("xmake", {"addon", "--remove", depname})
+ end, {deps = {hello = {2}}})
+end
+
+-- the plugins and templates are not namespaced, the conflicts should be rejected when installing
+function test_install_conflicts(t)
+ _with_repo({"hello"}, function (_, names)
+ local name = names[1]
+ os.runv("xmake", {"addon", "--install", "-y", name})
+
+ -- install another addon which provides the same plugin name
+ local othername = name .. "-other"
+ local dir = path.join(os.tmpfile() .. ".addon-conflict", othername)
+ _write_plugin(path.join(dir, "plugins", name), name)
+ _write_module(dir, name)
+ try
+ {
+ function ()
+ -- it should be rejected
+ t:require_not(try { function () os.runv("xmake", {"addon", "--install", dir}); return true end })
+
+ -- and the other commands should still work
+ t:require(os.iorunv("xmake", {"addon", "--list"}):find(name, 1, true))
+ t:require(os.iorunv("xmake", {name}):find(name, 1, true))
+ end,
+ finally
+ {
+ function ()
+ try { function () os.runv("xmake", {"addon", "--remove", "--force", othername}) end }
+ os.tryrm(path.directory(dir))
+ end
+ }
+ }
+
os.runv("xmake", {"addon", "--remove", name})
end)
end
@@ -148,15 +412,24 @@ function test_install_from_local(t)
local name = "hello-local-" .. suffix
local dir = path.join(os.tmpfile() .. ".addon-local", name)
_write_addon(dir, name)
+ try
+ {
+ function ()
+ os.runv("xmake", {"addon", "--install", dir})
+ t:require(os.iorunv("xmake", {name}):find(name, 1, true))
- os.runv("xmake", {"addon", "--install", dir})
- t:require(os.iorunv("xmake", {name}):find(name, 1, true))
-
- -- the removed addon should no longer be runnable
- os.runv("xmake", {"addon", "--remove", name})
- t:require_not(try { function () os.runv("xmake", {name}); return true end })
-
- os.tryrm(path.directory(dir))
+ -- the removed addon should no longer be runnable
+ os.runv("xmake", {"addon", "--remove", name})
+ t:require_not(try { function () os.runv("xmake", {name}); return true end })
+ end,
+ finally
+ {
+ function ()
+ try { function () os.runv("xmake", {"addon", "--remove", name}) end }
+ os.tryrm(path.directory(dir))
+ end
+ }
+ }
end
-- --list shows the installed addons and their payloads
@@ -169,7 +442,7 @@ function test_list(t)
t:require(out:find("the installed addons:", 1, true))
t:require(out:find(names[1], 1, true))
- -- the payloads of the installed addon should be shown, e.g. (plugins, templates)
+ -- the payloads of the installed addon should be shown, e.g. (plugins, rules, templates)
t:require(out:find("plugins", 1, true))
t:require(out:find("templates", 1, true))
@@ -194,6 +467,15 @@ function test_search(t)
end)
end
+-- the `addon` package name is reserved for the addon references
+function test_reserved_name(t)
+ t:require_not(try { function () _config_project([[
+add_requires("addon")
+target("test")
+ set_kind("phony")
+]]); return true end })
+end
+
-- invalid installs should fail
function test_install_invalid(t)
t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "addon-test-missing"}); return true end })