diff options
Diffstat (limited to 'tests')
28 files changed, 590 insertions, 118 deletions
diff --git a/tests/actions/addon/custom-include/addon.lua b/tests/actions/addon/custom-include/addon.lua new file mode 100644 index 000000000..71d01b5c2 --- /dev/null +++ b/tests/actions/addon/custom-include/addon.lua @@ -0,0 +1,2 @@ +addon("custom-include") + set_description("the addon which provides a custom includes file") diff --git a/tests/actions/addon/custom-include/includes/check/xmake.lua b/tests/actions/addon/custom-include/includes/check/xmake.lua new file mode 100644 index 000000000..953f7ba73 --- /dev/null +++ b/tests/actions/addon/custom-include/includes/check/xmake.lua @@ -0,0 +1,4 @@ +option("myoption") + set_default(true) + set_description("The option of the custom-include addon.") +print("custom-include: includes check is loaded") diff --git a/tests/actions/addon/custom-module/addon.lua b/tests/actions/addon/custom-module/addon.lua new file mode 100644 index 000000000..604dbc645 --- /dev/null +++ b/tests/actions/addon/custom-module/addon.lua @@ -0,0 +1,2 @@ +addon("custom-module") + set_description("the addon which provides a custom module") diff --git a/tests/actions/addon/custom-module/modules/greeting.lua b/tests/actions/addon/custom-module/modules/greeting.lua new file mode 100644 index 000000000..843dddfc2 --- /dev/null +++ b/tests/actions/addon/custom-module/modules/greeting.lua @@ -0,0 +1,3 @@ +function main(name) + return "hello from custom-module: " .. name +end diff --git a/tests/actions/addon/custom-plugin/addon.lua b/tests/actions/addon/custom-plugin/addon.lua new file mode 100644 index 000000000..ba4515c78 --- /dev/null +++ b/tests/actions/addon/custom-plugin/addon.lua @@ -0,0 +1,2 @@ +addon("custom-plugin") + set_description("the addon which provides a custom plugin") diff --git a/tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua b/tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua new file mode 100644 index 000000000..0cf3a0386 --- /dev/null +++ b/tests/actions/addon/custom-plugin/plugins/hello_addon/main.lua @@ -0,0 +1,5 @@ +import("core.base.option") + +function main() + print("hello from custom-plugin: %s", option.get("name") or "world") +end diff --git a/tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua b/tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua new file mode 100644 index 000000000..7976a1d0e --- /dev/null +++ b/tests/actions/addon/custom-plugin/plugins/hello_addon/xmake.lua @@ -0,0 +1,5 @@ +task("hello_addon") + set_category("plugin") + set_menu {usage = "xmake hello_addon", description = "Say hello from the addon.", + options = {{'n', "name", "kv", nil, "Set the name."}}} + on_run("main") diff --git a/tests/actions/addon/custom-rule/addon.lua b/tests/actions/addon/custom-rule/addon.lua new file mode 100644 index 000000000..2e9b68ab5 --- /dev/null +++ b/tests/actions/addon/custom-rule/addon.lua @@ -0,0 +1,2 @@ +addon("custom-rule") + set_description("the addon which provides a custom rule") diff --git a/tests/actions/addon/custom-rule/modules/greeting.lua b/tests/actions/addon/custom-rule/modules/greeting.lua new file mode 100644 index 000000000..f247d21ca --- /dev/null +++ b/tests/actions/addon/custom-rule/modules/greeting.lua @@ -0,0 +1,3 @@ +function main(name) + return "hello from custom-rule: " .. name +end diff --git a/tests/actions/addon/custom-rule/rules/hello/xmake.lua b/tests/actions/addon/custom-rule/rules/hello/xmake.lua new file mode 100644 index 000000000..07638387d --- /dev/null +++ b/tests/actions/addon/custom-rule/rules/hello/xmake.lua @@ -0,0 +1,6 @@ +rule("hello") + on_load(function (target) + -- the addon code refers to its own resources with `@self` + import("@self.greeting") + print("custom-rule: %s", greeting(target:name())) + end) diff --git a/tests/actions/addon/custom-template/addon.lua b/tests/actions/addon/custom-template/addon.lua new file mode 100644 index 000000000..f01eda9c9 --- /dev/null +++ b/tests/actions/addon/custom-template/addon.lua @@ -0,0 +1,2 @@ +addon("custom-template") + set_description("the addon which provides a custom project template") diff --git a/tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c b/tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c new file mode 100644 index 000000000..115a2a792 --- /dev/null +++ b/tests/actions/addon/custom-template/templates/c/customaddon/hello/src/main.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main(int argc, char** argv) { + printf("hello from the custom-template addon!\n"); + return 0; +} diff --git a/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua b/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua new file mode 100644 index 000000000..33c36ec7e --- /dev/null +++ b/tests/actions/addon/custom-template/templates/c/customaddon/hello/xmake.lua @@ -0,0 +1,5 @@ +add_rules("mode.debug", "mode.release") + +target("${TARGET_NAME}") + set_kind("binary") + add_files("src/*.c") diff --git a/tests/actions/addon/custom-toolchain/addon.lua b/tests/actions/addon/custom-toolchain/addon.lua new file mode 100644 index 000000000..8537b3272 --- /dev/null +++ b/tests/actions/addon/custom-toolchain/addon.lua @@ -0,0 +1,6 @@ +addon("custom-toolchain") + set_description("the addon which provides a custom toolchain") + set_sourcedir("src") + -- the tool modules are imported with their plain names by the internal calls, + -- e.g. find_tool("mycl6x"), import("core.tools.mycl6x") + add_globalmodules("core.tools.mycl6x", "detect.tools.find_mycl6x") diff --git a/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua b/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua new file mode 100644 index 000000000..972242c98 --- /dev/null +++ b/tests/actions/addon/custom-toolchain/src/modules/core/tools/mycl6x.lua @@ -0,0 +1,12 @@ +-- the tool module of the my-c6000 toolchain +-- +-- @note our compiler behaves like gcc, it's just a wrapper of the host one, so we only +-- inherit it here, a real one implements the tool interfaces itself, e.g. `init`, +-- `nf_define`, `compargv`, `link`, @see tests/apis/custom_toolchain +-- +inherit("core.tools.gcc") + +-- init it +function init(self) + self:set("cxflags", "-DMY_C6000_TOOL") +end diff --git a/tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua b/tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua new file mode 100644 index 000000000..c0ed37914 --- /dev/null +++ b/tests/actions/addon/custom-toolchain/src/modules/detect/tools/find_mycl6x.lua @@ -0,0 +1,15 @@ +import("lib.detect.find_program") + +-- find the compiler of the my-c6000 toolchain +-- +-- @note it's just the host compiler, we only need a real program to build with, +-- the point of this test is that this finder itself comes from the addon +-- +function main(opt) + for _, name in ipairs({"gcc", "clang", "cc"}) do + local program = find_program(name, opt) + if program then + return program + end + end +end diff --git a/tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua b/tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua new file mode 100644 index 000000000..9b023bac7 --- /dev/null +++ b/tests/actions/addon/custom-toolchain/src/toolchains/my-c6000/xmake.lua @@ -0,0 +1,19 @@ +-- a custom toolchain distributed as an addon +-- +-- @see tests/apis/custom_toolchain for the same toolchain maintained inside a project, +-- distributing it as an addon is the recommended way +-- +toolchain("my-c6000") + set_kind("standalone") + set_description("the custom toolchain of the tests") + + set_toolset("cc", "mycl6x") + set_toolset("ld", "mycl6x") + + on_check(function (toolchain) + return import("lib.detect.find_tool")("mycl6x") + end) + + on_load(function (toolchain) + toolchain:add("cxflags", "-DMY_C6000") + end) diff --git a/tests/actions/addon/projects/autofetch-badinclude/xmake-addons.lua b/tests/actions/addon/projects/autofetch-badinclude/xmake-addons.lua new file mode 100644 index 000000000..6931855ae --- /dev/null +++ b/tests/actions/addon/projects/autofetch-badinclude/xmake-addons.lua @@ -0,0 +1,2 @@ +-- this file only declares the addons, it cannot reference them +includes("@addon/custom-include/check") diff --git a/tests/actions/addon/projects/autofetch-badinclude/xmake.lua b/tests/actions/addon/projects/autofetch-badinclude/xmake.lua new file mode 100644 index 000000000..0d94cf61c --- /dev/null +++ b/tests/actions/addon/projects/autofetch-badinclude/xmake.lua @@ -0,0 +1,2 @@ +target("test") + set_kind("phony") diff --git a/tests/actions/addon/projects/autofetch-badname/xmake-addons.lua b/tests/actions/addon/projects/autofetch-badname/xmake-addons.lua new file mode 100644 index 000000000..165325fb7 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-badname/xmake-addons.lua @@ -0,0 +1,2 @@ +-- the `addon` name is reserved for the addon references +add_addons("addon") diff --git a/tests/actions/addon/projects/autofetch-badname/xmake.lua b/tests/actions/addon/projects/autofetch-badname/xmake.lua new file mode 100644 index 000000000..0d94cf61c --- /dev/null +++ b/tests/actions/addon/projects/autofetch-badname/xmake.lua @@ -0,0 +1,2 @@ +target("test") + set_kind("phony") diff --git a/tests/actions/addon/projects/autofetch-build/src/main.c b/tests/actions/addon/projects/autofetch-build/src/main.c new file mode 100644 index 000000000..43509bfeb --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/src/main.c @@ -0,0 +1,14 @@ +// the addons must provide the option and the toolchain of this project +#ifndef MYOPTION +# error the option of the custom-include addon is not found! +#endif +#ifndef MY_C6000 +# error the toolchain of the custom-toolchain addon is not used! +#endif +#ifndef MY_C6000_TOOL +# error the tool module of the custom-toolchain addon is not used! +#endif + +int main(int argc, char** argv) { + return 0; +} diff --git a/tests/actions/addon/projects/autofetch-build/xmake-addons.lua b/tests/actions/addon/projects/autofetch-build/xmake-addons.lua new file mode 100644 index 000000000..2091e3b7f --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/xmake-addons.lua @@ -0,0 +1,2 @@ +-- the addons which this project needs, they are installed automatically when we load it +add_addons("custom-include", "custom-rule", "custom-toolchain") diff --git a/tests/actions/addon/projects/autofetch-build/xmake.lua b/tests/actions/addon/projects/autofetch-build/xmake.lua new file mode 100644 index 000000000..bad3f8928 --- /dev/null +++ b/tests/actions/addon/projects/autofetch-build/xmake.lua @@ -0,0 +1,11 @@ +-- the option comes from the includes file of an addon +includes("@addon/custom-include/check") + +target("hello") + set_kind("binary") + add_files("src/main.c") + add_rules("@addon/custom-rule/hello") + set_toolchains("@addon/custom-toolchain/my-c6000") + if has_config("myoption") then + add_defines("MYOPTION") + end diff --git a/tests/actions/addon/projects/autofetch/xmake-addons.lua b/tests/actions/addon/projects/autofetch/xmake-addons.lua new file mode 100644 index 000000000..a85e0fe11 --- /dev/null +++ b/tests/actions/addon/projects/autofetch/xmake-addons.lua @@ -0,0 +1 @@ +add_addons("custom-include") diff --git a/tests/actions/addon/projects/autofetch/xmake.lua b/tests/actions/addon/projects/autofetch/xmake.lua new file mode 100644 index 000000000..4eaca0785 --- /dev/null +++ b/tests/actions/addon/projects/autofetch/xmake.lua @@ -0,0 +1,5 @@ +-- the addon is installed automatically, so we can use its includes file here +includes("@addon/custom-include/check") + +target("test") + set_kind("phony") diff --git a/tests/actions/addon/test.lua b/tests/actions/addon/test.lua new file mode 100644 index 000000000..192230b08 --- /dev/null +++ b/tests/actions/addon/test.lua @@ -0,0 +1,450 @@ +import("core.base.global") +import("lib.detect.find_program") + +-- the addon fixtures, each of them provides one kind of payload only +-- +-- @note they are independent from each other, @see tests/actions/addon/custom-* + +function _addondir(name) + return path.join(os.scriptdir(), name) +end + +function _remove(name) + try { function () os.runv("xmake", {"addon", "--remove", "--force", name}) end } +end + +-- install the given addon fixture from its local directory +function _install(name) + _remove(name) + os.runv("xmake", {"addon", "--install", _addondir(name)}) +end + +-- run the given function with the addons installed, we always remove them again +function _with_addons(names, func) + for _, name in ipairs(names) do + _install(name) + end + try + { + func, + finally + { + -- @note try() swallows the errors if we do not re-raise them here + function (ok, errors) + for _, name in ipairs(names) do + _remove(name) + end + if not ok then + raise(errors) + end + end + } + } +end + +-- create a temporary repository which indexes the given addon fixtures, and register it +-- +-- @note the recipes only point at the fixtures with `set_sourcedir`, we need not generate any payload +function _with_repo(recipes, func) + local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") + local reponame = "addon-test-repo-" .. suffix + local repodir = os.tmpfile() .. ".addon-repo" + for name, body in pairs(recipes) do + io.writefile(path.join(repodir, "addons", name:sub(1, 1), name, "xmake.lua"), + ("package(%q)\n set_kind(\"addon\")\n set_description(\"the addon fixture of the tests\")\n %s\n"):format(name, body)) + end + + -- register the repository and clear the quick search cache, it will be rebuilt on the next search + local cachefile = path.join(global.cachedir(), "repository") + local cache = os.isfile(cachefile) and io.load(cachefile) or {} + cache.repositories = cache.repositories or {} + + -- a killed test run may leave its temporary repository registered, and a dangling + -- repository breaks every following xrepo command, so we drop them here + for name, dirs in pairs(cache.repositories) do + if name:startswith("addon-test-repo-") and not os.isdir(dirs[1]) then + cache.repositories[name] = nil + end + end + cache.repositories[reponame] = {repodir} + io.save(cachefile, cache) + os.tryrm(path.join(global.cachedir(), "quick_search")) + + try + { + function () + func(reponame) + end, + finally + { + -- @note try() swallows the errors if we do not re-raise them here + function (ok, errors) + for name, _ in pairs(recipes) do + _remove(name) + end + local cache = os.isfile(cachefile) and io.load(cachefile) or {} + if cache.repositories then + cache.repositories[reponame] = nil + end + io.save(cachefile, cache) + os.tryrm(path.join(global.cachedir(), "quick_search")) + os.tryrm(repodir) + if not ok then + raise(errors) + end + end + } + } +end + +-- run the given command in a temporary project and return its output +function _run_project(content, argv, opt) + opt = opt or {} + local projectdir = os.tmpfile() .. ".addon-project" + os.tryrm(projectdir) + io.writefile(path.join(projectdir, "xmake.lua"), content) + for file, filecontent in pairs(opt.files or {}) do + io.writefile(path.join(projectdir, file), filecontent) + end + local oldir = os.cd(projectdir) + local out, errors + try + { + function () out = os.iorunv("xmake", argv) 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 + +function _config_project(content) + return _run_project(content, {"config", "-y"}) +end + +-- copy the given fixture project to a temporary directory and run the given function in it +-- +-- @note we cannot run them in place, they would generate the lock and the build files, +-- @see tests/actions/addon/projects +-- +function _with_project(name, func) + local projectdir = os.tmpfile() .. ".addon-project" + os.tryrm(projectdir) + os.mkdir(projectdir) + os.cp(path.join(os.scriptdir(), "projects", name, "*"), projectdir) + local oldir = os.cd(projectdir) + try + { + function () + func(projectdir) + end, + finally + { + -- @note try() swallows the errors if we do not re-raise them here + function (ok, errors) + os.cd(oldir) + os.tryrm(projectdir) + if not ok then + raise(errors) + end + end + } + } +end + +-- only the payloads should be installed, our own files should not +function test_install(t) + _with_addons({"custom-toolchain"}, function () + + -- it has the `src` layout, e.g. set_sourcedir("src") + local installdir = path.join(global.directory(), "addons", "custom-toolchain", "latest") + t:require(os.isdir(path.join(installdir, "toolchains"))) + t:require(os.isdir(path.join(installdir, "modules"))) + for _, ourfile in ipairs({"src", "addon.lua"}) do + t:require_not(os.exists(path.join(installdir, ourfile))) + end + + -- the addon describes itself, we should get its description from the manifest + t:require(os.iorunv("xmake", {"addon", "--list"}):find("the addon which provides a custom toolchain", 1, true)) + end) +end + +-- an addon can provide a plugin, e.g. xmake hello_addon +function test_plugin(t) + _with_addons({"custom-plugin"}, function () + t:require(os.iorunv("xmake", {"hello_addon", "-n", "xmake"}):find("hello from custom-plugin: xmake", 1, true)) + end) + + -- it should not be runnable after removing it + t:require_not(try { function () os.runv("xmake", {"hello_addon"}); return true end }) +end + +-- an addon can provide a rule, e.g. add_rules("@addon/custom-rule/hello") +function test_rule(t) + local projectfile = [[ +target("test") + set_kind("phony") + add_rules("@addon/custom-rule/hello") +]] + _with_addons({"custom-rule"}, function () + -- the rule imports a module of its own addon with `@self` + t:require(_config_project(projectfile):find("custom-rule: hello from custom-rule: test", 1, true)) + end) + + -- it should fail if the addon which provides it is not installed + t:require_not(try { function () _config_project(projectfile); return true end }) +end + +-- an addon can provide a module, e.g. import("@addon.custom-module.greeting") +function test_module(t) + _with_addons({"custom-module"}, function () + local script = "import(\"@addon.custom-module.greeting\"); print(greeting(\"xmake\"))" + t:require(os.iorunv("xmake", {"lua", "-c", script}):find("hello from custom-module: xmake", 1, true)) + + -- the addon modules are namespaced, they cannot be imported with their plain names + t:require_not(try { function () os.runv("xmake", {"lua", "-c", "import(\"greeting\")"}); return true end }) + end) +end + +-- an addon can provide an includes file, e.g. includes("@addon/custom-include/check") +function test_include(t) + _with_addons({"custom-include"}, function () + local out = _config_project([[ +includes("@addon/custom-include/check") +target("test") + set_kind("phony") +]]) + t:require(out:find("custom-include: includes check is loaded", 1, true)) + end) +end + +-- an addon can provide a project template, e.g. xmake create -t customaddon.hello +function test_template(t) + _with_addons({"custom-template"}, function () + t:require(os.iorunv("xmake", {"create", "--list", "-l", "c"}):find("customaddon.hello", 1, true)) + + local projectdir = os.tmpfile() .. ".addon-project" + os.tryrm(projectdir) + os.runv("xmake", {"create", "-l", "c", "-t", "customaddon.hello", "-P", projectdir}) + t:require(os.isfile(path.join(projectdir, "src", "main.c"))) + os.tryrm(projectdir) + end) +end + +-- an addon can provide a toolchain and its tool modules +-- +-- @see tests/apis/custom_toolchain for the same toolchain maintained inside a project +function test_toolchain(t) + _with_addons({"custom-toolchain"}, function () + + -- the toolchain can be loaded with the addon name + local script = "import(\"core.tool.toolchain\"); print(toolchain.load(\"@addon/custom-toolchain/my-c6000\"):get(\"description\"))" + t:require(os.iorunv("xmake", {"lua", "-c", script}):find("the custom toolchain of the tests", 1, true)) + + -- its tool modules are exported by the manifest, so the internal calls can import them + -- with their plain names, e.g. add_globalmodules("core.tools.mycl6x") + -- + -- @note there is no builtin `mycl6x`, so they can only come from this addon + local script2 = "import(\"core.tools.mycl6x\"); assert(mycl6x.compargv, \"invalid tool module!\"); " .. + "import(\"lib.detect.find_tool\"); assert(find_tool(\"mycl6x\"), \"mycl6x not found!\")" + os.runv("xmake", {"lua", "-c", script2}) + + -- and a project can build with it, the flags of the toolchain and of its tool module + -- are both checked by the source file + -- + -- @note its compiler is just the host one, so we can only build with it if there is one + if find_program("gcc") or find_program("clang") or find_program("cc") then + _run_project([[ +target("test") + set_kind("binary") + set_toolchains("@addon/custom-toolchain/my-c6000") + add_files("src/main.c") +]], {"build", "-y"}, {files = {["src/main.c"] = [[ +#ifndef MY_C6000 +# error the flags of the addon toolchain are not used! +#endif +#ifndef MY_C6000_TOOL +# error the flags of its tool module are not used! +#endif +int main(int argc, char** argv) { return 0; } +]]}}) + end + end) +end + +-- the addons which a project declares in `xmake-addons.lua` are installed automatically +-- +-- @note they must be installed before loading the project, it may use their includes files +function test_autofetch(t) + local recipes = {["custom-include"] = ("set_sourcedir(%q)"):format(_addondir("custom-include"))} + _with_repo(recipes, function () + _remove("custom-include") + _with_project("autofetch", function (projectdir) + + -- it should be installed when loading the project, so that its includes file can be found, + -- and we should tell the user why we install something, it may need to confirm and download + local output = os.iorunv("xmake", {"config", "-y"}) + t:require(output:find("custom-include: includes check is loaded", 1, true)) + t:require(output:find("this project needs the addons", 1, true)) + + -- and it should be locked + local lockfile = path.join(projectdir, "xmake-addons.lock") + t:require(os.isfile(lockfile)) + t:require(io.load(lockfile)["custom-include"] ~= nil) + + -- we should not install it again + t:require_not(os.iorunv("xmake", {"config", "-y"}):find("install custom-include", 1, true)) + end) + end) +end + +-- every command builds the option menu, which merges the project tasks in a best-effort way, +-- so the commands which need not the project should never install its addons +function test_autofetch_skipped_for_option_menu(t) + local recipes = {["custom-include"] = ("set_sourcedir(%q)"):format(_addondir("custom-include"))} + _with_repo(recipes, function () + _remove("custom-include") + _with_project("autofetch", function (projectdir) + os.runv("xmake", {"addon", "--list"}) + os.runv("xmake", {"lua", "-c", "print(\"hello\")"}) + t:require_not(os.isfile(path.join(projectdir, "xmake-addons.lock"))) + end) + end) +end + +-- a complete project which declares its addons in `xmake-addons.lua` and builds with them, +-- @see tests/actions/addon/projects/autofetch-build +function test_autofetch_build(t) + + -- @note the compiler of the custom toolchain is just the host one, + -- so we can only build it if there is one + if not (find_program("gcc") or find_program("clang") or find_program("cc")) then + return + end + + local names = {"custom-include", "custom-rule", "custom-toolchain"} + local recipes = {} + for _, name in ipairs(names) do + recipes[name] = ("set_sourcedir(%q)"):format(_addondir(name)) + end + _with_repo(recipes, function () + for _, name in ipairs(names) do + _remove(name) + end + _with_project("autofetch-build", function (projectdir) + + -- all of them should be installed when loading the project, and it should build + -- with their includes file, rule and toolchain, @see src/main.c + local output = os.iorunv("xmake", {"build", "-y"}) + t:require(output:find("custom-include: includes check is loaded", 1, true)) + t:require(output:find("custom-rule: hello from custom-rule: hello", 1, true)) + t:require(output:find("build ok", 1, true)) + + -- and all of them should be locked + local lockinfo = io.load(path.join(projectdir, "xmake-addons.lock")) + for _, name in ipairs(names) do + t:require(lockinfo[name] ~= nil) + end + end) + end) +end + +-- the addons file only declares the addons, it cannot reference them +function test_autofetch_invalid(t) + for _, name in ipairs({"autofetch-badinclude", "autofetch-badname"}) do + _with_project(name, function () + t:require_not(try { function () os.runv("xmake", {"config", "-y"}); return true end }) + end) + end +end + +-- the addons can be installed from a repository, by plain name and by repo@name +function test_install_from_repo(t) + local recipes = {["custom-plugin"] = ("set_sourcedir(%q)"):format(_addondir("custom-plugin")), + ["custom-module"] = ("set_sourcedir(%q)"):format(_addondir("custom-module"))} + _with_repo(recipes, function (reponame) + os.runv("xmake", {"addon", "--install", "-y", "custom-plugin"}) + t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true)) + + os.runv("xmake", {"addon", "--remove", "custom-plugin"}) + os.runv("xmake", {"addon", "--install", "-y", reponame .. "@custom-plugin"}) + t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true)) + + -- it should be searchable, and the addons should not be found by the package search + -- + -- @note we cannot run the `xrepo` program here, it may not be in the PATH, e.g. on the ci, + -- and `xrepo search` is just a wrapper of it + -- + t:require(os.iorunv("xmake", {"addon", "--search", "custom-plugin"}):find("custom-plugin", 1, true)) + t:require_not(os.iorunv("xmake", {"lua", "private.xrepo", "search", "custom-plugin"}):find("custom-plugin", 1, true)) + + -- and several of them can be installed in one shot, they are resolved and installed together + os.runv("xmake", {"addon", "--remove", "--force", "custom-plugin"}) + os.runv("xmake", {"addon", "--install", "-y", "custom-plugin", reponame .. "@custom-module"}) + t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true)) + t:require(os.iorunv("xmake", {"lua", "-c", "import(\"@addon.custom-module.greeting\"); print(greeting(\"xmake\"))"}) + :find("hello from custom-module: xmake", 1, true)) + end) +end + +-- an addon can depend on the other addons, they are installed and activated together +function test_addon_deps(t) + local recipes = { + ["custom-module"] = ("set_sourcedir(%q)"):format(_addondir("custom-module")), + ["custom-plugin"] = ("set_sourcedir(%q)\n add_deps(\"custom-module\", {kind = \"addon\"})"):format(_addondir("custom-plugin")) + } + _with_repo(recipes, function () + os.runv("xmake", {"addon", "--install", "-y", "custom-plugin"}) + local out = os.iorunv("xmake", {"addon", "--list"}) + t:require(out:find("custom-plugin", 1, true)) + t:require(out:find("custom-module", 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", "custom-module"}); return true end }) + end) +end + +-- the plugins and the templates are not namespaced, the conflicts should be rejected when installing +function test_install_conflicts(t) + _with_addons({"custom-plugin"}, function () + + -- this addon provides the same plugin name + local clonedir = os.tmpfile() .. ".addon-clone" + os.tryrm(clonedir) + io.writefile(path.join(clonedir, "addon.lua"), "addon(\"custom-plugin-clone\")\n") + io.writefile(path.join(clonedir, "plugins", "hello_addon", "xmake.lua"), + "task(\"hello_addon\")\n set_category(\"plugin\")\n set_menu {}\n on_run(function () end)\n") + t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", clonedir}); return true end }) + os.tryrm(clonedir) + + -- and the other commands should still work + t:require(os.iorunv("xmake", {"hello_addon"}):find("hello from custom-plugin", 1, true)) + end) +end + +-- the invalid installs should fail, and the `addon` name is reserved for the addon references +function test_invalid(t) + t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "addon-test-missing"}); return true end }) + t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "somerepo@.."}); return true end }) + + -- the addon name in its manifest must match the package name which distributes it + local recipes = {["custom-plugin-badname"] = ("set_sourcedir(%q)"):format(_addondir("custom-plugin"))} + _with_repo(recipes, function () + t:require_not(try { function () os.runv("xmake", {"addon", "--install", "-y", "custom-plugin-badname"}); return true end }) + end) + + -- the `addon` name is reserved + t:require_not(try { function () _config_project([[ +add_requires("addon") +target("test") + set_kind("phony") +]]); return true end }) +end diff --git a/tests/plugins/repository/test.lua b/tests/plugins/repository/test.lua deleted file mode 100644 index 7b1619d5e..000000000 --- a/tests/plugins/repository/test.lua +++ /dev/null @@ -1,118 +0,0 @@ -import("core.base.global") - --- write a minimal plugin that prints its name when run -function _write_plugin(dir, name) - io.writefile(path.join(dir, "xmake.lua"), string.format([[ -task("%s") - set_category("plugin") - 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)) -end - --- write a plugin package description, the plugin sources are placed in its `src` directory --- --- plugins in a repository are described as packages, e.g. <repodir>/plugins/<first-letter>/<name>/xmake.lua -function _write_plugin_package(dir, name) - io.writefile(path.join(dir, "xmake.lua"), string.format([[ -package("%s") - set_kind("plugin") - set_description("say hello from %s") - set_sourcedir(path.join(os.scriptdir(), "src")) -]], name, name)) - _write_plugin(path.join(dir, "src"), name) -end - --- create a temporary plugin repository (packages layout: plugins/<first-letter>/<name>) and register it --- --- @return reponame, names, cleanup -function _mock_repo(basenames) - local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") - local reponame = "plugin-test-repo-" .. suffix - local repodir = os.tmpfile() .. ".plugin-repo" - local names = {} - for _, base in ipairs(basenames) do - local name = base .. "-" .. suffix - _write_plugin_package(path.join(repodir, "plugins", name:sub(1, 1), name), name) - table.insert(names, name) - end - - -- register the repository into the cache - local cachefile = path.join(global.cachedir(), "repository") - local cache = os.isfile(cachefile) and io.load(cachefile) or {} - cache.repositories = cache.repositories or {} - cache.repositories[reponame] = {repodir} - io.save(cachefile, cache) - - local function cleanup() - for _, name in ipairs(names) do - os.tryrm(path.join(global.directory(), "plugins", name)) - end - local cache = os.isfile(cachefile) and io.load(cachefile) or {} - if cache.repositories then - cache.repositories[reponame] = nil - end - io.save(cachefile, cache) - os.tryrm(repodir) - end - return reponame, names, cleanup -end - --- install a plugin from a repository, by plain name and by repo@name -function test_install_from_repo(t) - local reponame, names, cleanup = _mock_repo({"hello"}) - local name = names[1] - - -- install by plain name (searched across all repositories) - os.runv("xmake", {"plugin", "--install", "-y", name}) - t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - - -- reinstall by repo@name - os.runv("xmake", {"plugin", "--remove", name}) - os.runv("xmake", {"plugin", "--install", "-y", reponame .. "@" .. name}) - t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - - os.runv("xmake", {"plugin", "--remove", name}) - cleanup() -end - --- install a plugin from a local directory, then remove it -function test_install_from_local(t) - local suffix = path.filename(os.tmpfile()):gsub("[^%w]", "") - local name = "hello-local-" .. suffix - local dir = path.join(os.tmpfile() .. ".plugin-local", name) - _write_plugin(dir, name) - - os.runv("xmake", {"plugin", "--install", dir}) - t:require(os.iorunv("xmake", {name}):find(name, 1, true)) - - -- the removed plugin should no longer be runnable - os.runv("xmake", {"plugin", "--remove", name}) - t:require_not(try { function () os.runv("xmake", {name}); return true end }) - - os.tryrm(path.directory(dir)) -end - --- --list shows the built-in, installed and available plugins -function test_list(t) - local reponame, names, cleanup = _mock_repo({"hello", "world"}) - - -- install the first plugin, leave the second only available - os.runv("xmake", {"plugin", "--install", "-y", names[1]}) - local out = os.iorunv("xmake", {"plugin", "--list"}) - t:require(out:find("the built-in plugins:", 1, true)) - t:require(out:find("project", 1, true)) - t:require(out:find(names[1], 1, true)) - t:require(out:find(names[2], 1, true)) - t:require(out:find("xmake plugin --install " .. names[2], 1, true)) - - os.runv("xmake", {"plugin", "--remove", names[1]}) - cleanup() -end - --- invalid installs should fail -function test_install_invalid(t) - t:require_not(try { function () os.runv("xmake", {"plugin", "--install", "-y", "plugin-test-missing"}); return true end }) - t:require_not(try { function () os.runv("xmake", {"plugin", "--install", "-y", "somerepo@.."}); return true end }) -end |
