summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2023-10-07 16:57:13 +0800
committerGitHub <[email protected]>2023-10-07 16:57:13 +0800
commit720f0eb9c5ba51a5a76ef31e7285b6ea1b08b400 (patch)
tree4b324d9ecf8c561712cc6531a3d28ded21ac2045
parentbceea4e7fe7553b90ac7d14e4ea854bbb2d3e04a (diff)
parent2bfdd8a805baa9753ffed3dac771b6f3dce077bf (diff)
Merge pull request #4259 from xmake-io/tests
Add `xmake test` support
-rw-r--r--CHANGELOG.md2
-rw-r--r--tests/actions/test/.gitignore8
-rw-r--r--tests/actions/test/src/test_1.cpp10
-rw-r--r--tests/actions/test/src/test_2.cpp10
-rw-r--r--tests/actions/test/src/test_3.cpp10
-rw-r--r--tests/actions/test/src/test_4.cpp10
-rw-r--r--tests/actions/test/src/test_5.cpp10
-rw-r--r--tests/actions/test/src/test_6.cpp10
-rw-r--r--tests/actions/test/src/test_7.cpp10
-rw-r--r--tests/actions/test/src/test_8.cpp9
-rw-r--r--tests/actions/test/src/test_9.cpp10
-rw-r--r--tests/actions/test/test.lua4
-rw-r--r--tests/actions/test/xmake.lua14
-rw-r--r--xmake/actions/build/build.lua26
-rw-r--r--xmake/actions/build/check.lua8
-rw-r--r--xmake/actions/build/main.lua96
-rw-r--r--xmake/actions/run/main.lua4
-rw-r--r--xmake/actions/test/main.lua362
-rw-r--r--xmake/actions/test/xmake.lua48
-rw-r--r--xmake/core/project/rule.lua3
-rw-r--r--xmake/core/project/target.lua4
21 files changed, 606 insertions, 62 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6324e935f..00bd1580d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
* [#4250](https://github.com/xmake-io/xmake/pull/4250): Improve link mechanism and order
* [#1438](https://github.com/xmake-io/xmake/issues/1438): Support code amalgamation
+* [#3381](https://github.com/xmake-io/xmake/issues/3381): Add `xmake test` support
## v2.8.3
@@ -1671,6 +1672,7 @@
* [#4250](https://github.com/xmake-io/xmake/pull/4250): 支持链接顺序调整,链接组
* [#1438](https://github.com/xmake-io/xmake/issues/1438): 支持代码 amalgamation
+* [#3381](https://github.com/xmake-io/xmake/issues/3381): 添加 `xmake test` 支持
## v2.8.3
diff --git a/tests/actions/test/.gitignore b/tests/actions/test/.gitignore
new file mode 100644
index 000000000..152105761
--- /dev/null
+++ b/tests/actions/test/.gitignore
@@ -0,0 +1,8 @@
+# Xmake cache
+.xmake/
+build/
+
+# MacOS Cache
+.DS_Store
+
+
diff --git a/tests/actions/test/src/test_1.cpp b/tests/actions/test/src/test_1.cpp
new file mode 100644
index 000000000..f454e99f3
--- /dev/null
+++ b/tests/actions/test/src/test_1.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_2.cpp b/tests/actions/test/src/test_2.cpp
new file mode 100644
index 000000000..f454e99f3
--- /dev/null
+++ b/tests/actions/test/src/test_2.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_3.cpp b/tests/actions/test/src/test_3.cpp
new file mode 100644
index 000000000..f454e99f3
--- /dev/null
+++ b/tests/actions/test/src/test_3.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_4.cpp b/tests/actions/test/src/test_4.cpp
new file mode 100644
index 000000000..f454e99f3
--- /dev/null
+++ b/tests/actions/test/src/test_4.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_5.cpp b/tests/actions/test/src/test_5.cpp
new file mode 100644
index 000000000..dad841287
--- /dev/null
+++ b/tests/actions/test/src/test_5.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello2 " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_6.cpp b/tests/actions/test/src/test_6.cpp
new file mode 100644
index 000000000..f454e99f3
--- /dev/null
+++ b/tests/actions/test/src/test_6.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_7.cpp b/tests/actions/test/src/test_7.cpp
new file mode 100644
index 000000000..dae685668
--- /dev/null
+++ b/tests/actions/test/src/test_7.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return -1;
+}
diff --git a/tests/actions/test/src/test_8.cpp b/tests/actions/test/src/test_8.cpp
new file mode 100644
index 000000000..8669ff781
--- /dev/null
+++ b/tests/actions/test/src/test_8.cpp
@@ -0,0 +1,9 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ cout << "hello xmake" << endl;
+ return 0;
+}
diff --git a/tests/actions/test/src/test_9.cpp b/tests/actions/test/src/test_9.cpp
new file mode 100644
index 000000000..f454e99f3
--- /dev/null
+++ b/tests/actions/test/src/test_9.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ char const* arg = argc > 1? argv[1] : "xmake";
+ cout << "hello " << arg << endl;
+ return 0;
+}
diff --git a/tests/actions/test/test.lua b/tests/actions/test/test.lua
new file mode 100644
index 000000000..45ce0b914
--- /dev/null
+++ b/tests/actions/test/test.lua
@@ -0,0 +1,4 @@
+function main(t)
+ os.exec("xmake test")
+end
+
diff --git a/tests/actions/test/xmake.lua b/tests/actions/test/xmake.lua
new file mode 100644
index 000000000..086734631
--- /dev/null
+++ b/tests/actions/test/xmake.lua
@@ -0,0 +1,14 @@
+add_rules("mode.debug", "mode.release")
+
+for _, file in ipairs(os.files("src/test_*.cpp")) do
+ local name = path.basename(file)
+ target(name)
+ set_kind("binary")
+ set_default(false)
+ add_files("src/" .. name .. ".cpp")
+ add_tests("default")
+ add_tests("args", {runargs = {"foo", "bar"}})
+ add_tests("pass_output", {trim_output = true, runargs = "foo", pass_outputs = "hello foo"})
+ add_tests("fail_output", {fail_outputs = {"hello2 .*", "hello xmake"}})
+end
+
diff --git a/xmake/actions/build/build.lua b/xmake/actions/build/build.lua
index 5cea43185..bb0b61cc1 100644
--- a/xmake/actions/build/build.lua
+++ b/xmake/actions/build/build.lua
@@ -229,19 +229,21 @@ function _add_batchjobs_for_target_and_deps(batchjobs, rootjob, jobrefs, target)
end
-- get batch jobs, @note we need to export it for private.diagnosis.dump_buildjobs
-function get_batchjobs(targetname, group_pattern)
+function get_batchjobs(targetnames, group_pattern)
-- get root targets
local targets_root = {}
- if targetname then
- local target = project.target(targetname)
- if target then
- table.insert(targets_root, target)
- if option.get("rebuild") then
- target:data_set("rebuilt", true)
- if not option.get("shallow") then
- for _, dep in ipairs(target:orderdeps()) do
- dep:data_set("rebuilt", true)
+ if targetnames then
+ for _, targetname in ipairs(table.wrap(targetnames)) do
+ local target = project.target(targetname)
+ if target then
+ table.insert(targets_root, target)
+ if option.get("rebuild") then
+ target:data_set("rebuilt", true)
+ if not option.get("shallow") then
+ for _, dep in ipairs(target:orderdeps()) do
+ dep:data_set("rebuilt", true)
+ end
end
end
end
@@ -280,7 +282,7 @@ function get_batchjobs(targetname, group_pattern)
end
-- the main entry
-function main(targetname, group_pattern)
+function main(targetnames, group_pattern)
-- enable distcc?
local distcc
@@ -289,7 +291,7 @@ function main(targetname, group_pattern)
end
-- build all jobs
- local batchjobs = get_batchjobs(targetname, group_pattern)
+ local batchjobs = get_batchjobs(targetnames, group_pattern)
if batchjobs and batchjobs:size() > 0 then
local curdir = os.curdir()
runjobs("build", batchjobs, {on_exit = function (errors)
diff --git a/xmake/actions/build/check.lua b/xmake/actions/build/check.lua
index e26e4f0d0..f468acfb1 100644
--- a/xmake/actions/build/check.lua
+++ b/xmake/actions/build/check.lua
@@ -44,13 +44,15 @@ function _show(str, opt)
end
end
-function main(targetname, opt)
+function main(targetnames, opt)
opt = opt or {}
-- get targets
local targets = {}
- if targetname then
- table.insert(targets, project.target(targetname))
+ if targetnames then
+ for _, targetname in ipairs(table.wrap(targetnames)) do
+ table.insert(targets, project.target(targetname))
+ end
else
for _, target in pairs(project.targets()) do
if target:is_enabled() then
diff --git a/xmake/actions/build/main.lua b/xmake/actions/build/main.lua
index 58456ba70..43897578a 100644
--- a/xmake/actions/build/main.lua
+++ b/xmake/actions/build/main.lua
@@ -129,61 +129,26 @@ function _on_exit(ok, errors)
end
end
--- main
-function main()
-
- -- try building it using third-party buildsystem if xmake.lua not exists
- if not os.isfile(project.rootfile()) and _try_build() then
- return
- end
-
- -- post statistics before locking project
- statistics.post()
-
- -- do action for remote?
- if remote_build_action.enabled() then
- return remote_build_action()
- end
-
- -- lock the whole project
- project.lock()
-
- -- config it first
- local targetname
- local group_pattern = option.get("group")
- if group_pattern then
- group_pattern = "^" .. path.pattern(group_pattern) .. "$"
- else
- targetname = option.get("target")
- end
- task.run("config", {}, {disable_dump = true})
-
- -- enter project directory
- local oldir = os.cd(project.directory())
-
- -- clean up temporary files once a day
- cleaner.cleanup()
+-- build targets
+function build_targets(targetnames, opt)
+ opt = opt or {}
-- register exit callbacks
os.atexit(_on_exit)
- local build_time
+ local group_pattern = opt.group_pattern
try
{
function ()
- local time = os.mclock()
-- do rules before building
_do_project_rules("build_before")
-- do build
- _do_build(targetname, group_pattern)
+ _do_build(targetnames, group_pattern)
-- do check
- check_targets(targetname, {build = true})
-
- -- get build time
- build_time = os.mclock() - time
+ check_targets(targetnames, {build = true})
-- dump cache stats
if option.get("diagnosis") then
@@ -206,8 +171,9 @@ function main()
raise(errors)
elseif group_pattern then
raise("build targets with group(%s) failed!", group_pattern)
- elseif targetname then
- raise("build target: %s failed!", targetname)
+ elseif targetnames then
+ targetnames = table.wrap(targetnames)
+ raise("build target: %s failed!", table.concat(targetnames, ", "))
else
raise("build target failed!")
end
@@ -217,13 +183,53 @@ function main()
-- do rules after building
_do_project_rules("build_after")
+end
- -- unlock the whole project
- project.unlock()
+function main()
+
+ -- try building it using third-party buildsystem if xmake.lua not exists
+ if not os.isfile(project.rootfile()) and _try_build() then
+ return
+ end
+
+ -- post statistics before locking project
+ statistics.post()
+
+ -- do action for remote?
+ if remote_build_action.enabled() then
+ return remote_build_action()
+ end
+
+ -- lock the whole project
+ project.lock()
+
+ -- config it first
+ local targetname
+ local group_pattern = option.get("group")
+ if group_pattern then
+ group_pattern = "^" .. path.pattern(group_pattern) .. "$"
+ else
+ targetname = option.get("target")
+ end
+ task.run("config", {}, {disable_dump = true})
+
+ -- enter project directory
+ local oldir = os.cd(project.directory())
+
+ -- clean up temporary files once a day
+ cleaner.cleanup()
+
+ -- build targets
+ local build_time = os.mclock()
+ build_targets(targetname, {group_pattern = group_pattern})
+ build_time = os.mclock() - build_time
-- leave project directory
os.cd(oldir)
+ -- unlock the whole project
+ project.unlock()
+
-- trace
local str = ""
if build_time then
diff --git a/xmake/actions/run/main.lua b/xmake/actions/run/main.lua
index 70a277a86..217f87c66 100644
--- a/xmake/actions/run/main.lua
+++ b/xmake/actions/run/main.lua
@@ -44,7 +44,7 @@ function _do_run_target(target)
-- get the absolute target file path
local targetfile = path.absolute(target:targetfile())
- -- build run environments
+ -- get the run environments
local addenvs, setenvs = runenvs.make(target)
-- get run arguments
@@ -77,7 +77,7 @@ function _on_run_target(target)
_do_run_target(target)
end
--- recursively target add env
+-- recursively add target envs
function _add_target_pkgenvs(target, targets_added)
if targets_added[target:name()] then
return
diff --git a/xmake/actions/test/main.lua b/xmake/actions/test/main.lua
new file mode 100644
index 000000000..d39c80a18
--- /dev/null
+++ b/xmake/actions/test/main.lua
@@ -0,0 +1,362 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file main.lua
+--
+
+-- imports
+import("core.base.option")
+import("core.base.task")
+import("core.project.config")
+import("core.base.global")
+import("core.project.project")
+import("core.platform.platform")
+import("core.theme.theme")
+import("async.runjobs")
+import("private.action.run.runenvs")
+import("private.service.remote_build.action", {alias = "remote_build_action"})
+import("actions.build.main", {rootdir = os.programdir(), alias = "build_action"})
+
+-- test target
+function _do_test_target(target, opt)
+ opt = opt or {}
+
+ -- get run environments
+ local envs = opt.runenvs
+ if not envs then
+ local addenvs, setenvs = runenvs.make(target)
+ envs = runenvs.join(addenvs, setenvs)
+ end
+
+ -- run test
+ local outdata
+ local errors
+ local rundir = opt.rundir or target:rundir()
+ local targetfile = path.absolute(target:targetfile())
+ local runargs = table.wrap(opt.runargs or target:get("runargs"))
+ local outfile = os.tmpfile()
+ local errfile = os.tmpfile()
+ local ok, syserrors = os.execv(targetfile, runargs, {try = true, curdir = rundir, envs = envs, stdout = outfile, stderr = errfile})
+ local outdata = os.isfile(outfile) and io.readfile(outfile) or ""
+ if opt.trim_output then
+ outdata = outdata:trim()
+ end
+ if ok ~= 0 then
+ local errdata = os.isfile(errfile) and io.readfile(errfile)
+ errors = errdata or errors
+ if not errors or #errors == 0 then
+ if ok ~= nil then
+ errors = string.format("run failed, exit code: %d", ok)
+ else
+ errors = string.format("run failed, exit error: %s", syserrors and syserrors or "unknown reason")
+ end
+ end
+ end
+ os.tryrm(outfile)
+ os.tryrm(errfile)
+
+ if ok == 0 then
+ local passed
+ local pass_outputs = table.wrap(opt.pass_outputs)
+ local fail_outputs = table.wrap(opt.fail_outputs)
+ for _, pass_output in ipairs(pass_outputs) do
+ if opt.plain then
+ if pass_output == outdata then
+ passed = true
+ break
+ end
+ else
+ if outdata:match("^" .. pass_output .. "$") then
+ passed = true
+ break
+ end
+ end
+ end
+ for _, fail_output in ipairs(fail_outputs) do
+ if opt.plain then
+ if fail_output == outdata then
+ passed = false
+ if not errors or #errors == 0 then
+ errors = string.format("matched failed output: ${color.failure}%s${clear}", fail_output)
+ local actual_output = outdata
+ if not option.get("diagnosis") then
+ actual_output = outdata:sub(1, 64)
+ if #outdata > #actual_output then
+ actual_output = actual_output .. "..."
+ end
+ end
+ errors = errors .. ", actual output: ${color.failure}" .. actual_output
+ end
+ break
+ end
+ else
+ if outdata:match("^" .. fail_output .. "$") then
+ passed = false
+ if not errors or #errors == 0 then
+ errors = string.format("matched failed output: ${color.failure}%s${clear}", fail_output)
+ local actual_output = outdata
+ if not option.get("diagnosis") then
+ actual_output = outdata:sub(1, 64)
+ if #outdata > #actual_output then
+ actual_output = actual_output .. "..."
+ end
+ end
+ errors = errors .. ", actual output: ${color.failure}" .. actual_output
+ end
+ break
+ end
+ end
+ end
+ if passed == nil then
+ if #pass_outputs == 0 then
+ passed = true
+ else
+ passed = false
+ if not errors or #errors == 0 then
+ errors = string.format("not matched passed output: ${color.success}%s${clear}", table.concat(pass_outputs, ", "))
+ local actual_output = outdata
+ if not option.get("diagnosis") then
+ actual_output = outdata:sub(1, 64)
+ if #outdata > #actual_output then
+ actual_output = actual_output .. "..."
+ end
+ end
+ errors = errors .. ", actual output: ${color.failure}" .. actual_output
+ end
+ end
+ end
+ return passed, errors
+ end
+ return false, errors
+end
+
+-- test target
+function _on_test_target(target, opt)
+
+ -- build target with rules
+ local passed
+ local errors
+ local done = false
+ for _, r in ipairs(target:orderules()) do
+ local on_test = r:script("test")
+ if on_test then
+ passed, errors = on_test(target, opt)
+ done = true
+ end
+ end
+ if done then
+ return passed, errors
+ end
+
+ -- do test
+ return _do_test_target(target, opt)
+end
+
+-- recursively add target envs
+function _add_target_pkgenvs(target, targets_added)
+ if targets_added[target:name()] then
+ return
+ end
+ targets_added[target:name()] = true
+ os.addenvs(target:pkgenvs())
+ for _, dep in ipairs(target:orderdeps()) do
+ _add_target_pkgenvs(dep, targets_added)
+ end
+end
+
+-- run the given test
+function _run_test(target, test)
+
+ -- enter the environments of the target packages
+ local oldenvs = os.getenvs()
+ _add_target_pkgenvs(target, {})
+
+ -- the target scripts
+ local scripts =
+ {
+ target:script("test_before")
+ , function (target, opt)
+ for _, r in ipairs(target:orderules()) do
+ local before_test = r:script("test_before")
+ if before_test then
+ before_test(target, opt)
+ end
+ end
+ end
+ , target:script("test", _on_test_target)
+ , function (target, opt)
+ for _, r in ipairs(target:orderules()) do
+ local after_test = r:script("test_after")
+ if after_test then
+ after_test(target, opt)
+ end
+ end
+ end
+ , target:script("test_after")
+ }
+
+ -- run the target scripts
+ local passed
+ local errors
+ for i = 1, 5 do
+ local script = scripts[i]
+ if script ~= nil then
+ local ok, errs = script(target, test)
+ if i == 3 then
+ passed = ok
+ errors = errs
+ end
+ end
+ end
+
+ -- leave the environments of the target packages
+ os.setenvs(oldenvs)
+ return passed, errors
+end
+
+-- run tests
+function _run_tests(tests)
+ local ordertests = {}
+ local maxwidth = 0
+ for name, testinfo in table.orderpairs(tests) do
+ table.insert(ordertests, testinfo)
+ if #testinfo.name > maxwidth then
+ maxwidth = #testinfo.name
+ end
+ end
+ if #ordertests == 0 then
+ print("nothing to test")
+ return
+ end
+
+ -- do test
+ local spent = os.mclock()
+ print("running tests ...")
+ local report = {passed = 0, total = #ordertests}
+ local jobs = tonumber(option.get("jobs") or "1")
+ runjobs("run_tests", function (index)
+ local testinfo = ordertests[index]
+ if testinfo then
+ local target = testinfo.target
+ testinfo.target = nil
+ local spent = os.mclock()
+ local passed, errors = _run_test(target, testinfo)
+ spent = os.mclock() - spent
+ if passed then
+ report.passed = report.passed + 1
+ end
+ local status_color = passed and "${color.success}" or "${color.failure}"
+ local progress_format = status_color .. theme.get("text.build.progress_format") .. ":${clear} "
+ if option.get("verbose") then
+ progress_format = progress_format .. "${dim}"
+ end
+ local progress = math.floor(index * 100 / #ordertests)
+ local padding = maxwidth - #testinfo.name
+ cprint(progress_format .. "%s%s .................................... " .. status_color .. "%s${clear} ${bright}%0.3fs",
+ progress, testinfo.name, (" "):rep(padding), passed and "passed" or "failed", spent)
+ if not passed and errors and (option.get("verbose") or option.get("diagnosis")) then
+ cprint(errors)
+ end
+ end
+ end, {total = #ordertests,
+ comax = jobs,
+ isolate = true})
+
+ -- generate report
+ spent = os.mclock() - spent
+ local passed_rate = math.floor(report.passed * 100 / report.total)
+ print("")
+ cprint("${color.success}%d%%${clear} tests passed, ${color.failure}%d${clear} tests failed out of ${bright}%d${clear}, spent ${bright}%0.3fs",
+ passed_rate, report.total - report.passed, report.total, spent / 1000)
+end
+
+function main()
+
+ -- do action for remote?
+ if remote_build_action.enabled() then
+ return remote_build_action()
+ end
+
+ -- lock the whole project
+ project.lock()
+
+ -- load config first
+ task.run("config", {}, {disable_dump = true})
+
+ -- load targets
+ project.load_targets()
+
+ -- get tests
+ local tests = {}
+ local group_pattern = option.get("group")
+ if group_pattern then
+ group_pattern = "^" .. path.pattern(group_pattern) .. "$"
+ end
+ for _, target in ipairs(project.ordertargets()) do
+ if target:is_binary() or target:script("run") then
+ for _, name in ipairs(target:get("tests")) do
+ local extra = target:extraconf("tests", name)
+ local testname = target:name() .. "/" .. name
+ local testinfo = {name = testname, target = target}
+ if extra then
+ table.join2(testinfo, extra)
+ end
+ if not testinfo.group then
+ testinfo.group = target:get("group")
+ end
+
+ local group = testinfo.group
+ if (not group_pattern) or (group_pattern and group and group:match(group_pattern)) then
+ tests[testname] = testinfo
+ end
+ end
+ end
+ end
+ local test_patterns = option.get("tests")
+ if test_patterns then
+ local tests_new = {}
+ for _, pattern in ipairs(test_patterns) do
+ pattern = "^" .. path.pattern(pattern) .. "$"
+ for name, testinfo in pairs(tests) do
+ if name:match(pattern) then
+ tests_new[name] = testinfo
+ end
+ end
+ end
+ tests = tests_new
+ end
+
+ -- enter project directory
+ local oldir = os.cd(project.directory())
+
+ -- build targets with the given tests first
+ local targetnames = {}
+ for _, testinfo in table.orderpairs(tests) do
+ table.insert(targetnames, testinfo.target:name())
+ end
+ build_action.build_targets(targetnames)
+
+ -- run tests
+ _run_tests(tests)
+
+ -- leave project directory
+ os.cd(oldir)
+
+ -- unlock the whole project
+ project.unlock()
+end
+
diff --git a/xmake/actions/test/xmake.lua b/xmake/actions/test/xmake.lua
new file mode 100644
index 000000000..c892d1840
--- /dev/null
+++ b/xmake/actions/test/xmake.lua
@@ -0,0 +1,48 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file xmake.lua
+--
+
+task("test")
+ set_category("action")
+ on_run("main")
+ set_menu {
+ usage = "xmake test [options] [target] [arguments]",
+ description = "Run the project tests.",
+ options = {
+ {'g', "group", "kv", nil , "Run all tests of the given group. It support path pattern matching.",
+ "e.g.",
+ " xmake test -g test",
+ " xmake test -g test_*",
+ " xmake test --group=benchmark/*" },
+ {'w', "workdir", "kv", nil , "Work directory of running targets, default is folder of targetfile",
+ "e.g.",
+ " xmake test -w .",
+ " xmake test --workdir=`pwd`" },
+ {'j', "jobs", "kv", "1", "Set the number of parallel compilation jobs." },
+ {},
+ {nil, "tests", "vs", nil , "The test names. It support pattern matching.",
+ "e.g.",
+ " xmake test foo",
+ " xmake test */foo",
+ " xmake test targetname/*" }
+ }
+ }
+
+
+
diff --git a/xmake/core/project/rule.lua b/xmake/core/project/rule.lua
index 4d2a058a0..57510b45a 100644
--- a/xmake/core/project/rule.lua
+++ b/xmake/core/project/rule.lua
@@ -308,6 +308,7 @@ function rule.apis()
{
-- rule.on_xxx
"rule.on_run"
+ , "rule.on_test"
, "rule.on_load"
, "rule.on_config"
, "rule.on_link"
@@ -324,6 +325,7 @@ function rule.apis()
, "rule.on_buildcmd_files"
-- rule.before_xxx
, "rule.before_run"
+ , "rule.before_test"
, "rule.before_load"
, "rule.before_link"
, "rule.before_build"
@@ -339,6 +341,7 @@ function rule.apis()
, "rule.before_buildcmd_files"
-- rule.after_xxx
, "rule.after_run"
+ , "rule.after_test"
, "rule.after_load"
, "rule.after_link"
, "rule.after_build"
diff --git a/xmake/core/project/target.lua b/xmake/core/project/target.lua
index 61501648f..607bc68cc 100644
--- a/xmake/core/project/target.lua
+++ b/xmake/core/project/target.lua
@@ -2455,6 +2455,7 @@ function target.apis()
, "target.add_languages"
, "target.add_vectorexts"
, "target.add_toolchains"
+ , "target.add_tests"
}
, keyvalues =
{
@@ -2493,6 +2494,7 @@ function target.apis()
{
-- target.on_xxx
"target.on_run"
+ , "target.on_test"
, "target.on_load"
, "target.on_config"
, "target.on_link"
@@ -2505,6 +2507,7 @@ function target.apis()
, "target.on_uninstall"
-- target.before_xxx
, "target.before_run"
+ , "target.before_test"
, "target.before_link"
, "target.before_build"
, "target.before_build_file"
@@ -2515,6 +2518,7 @@ function target.apis()
, "target.before_uninstall"
-- target.after_xxx
, "target.after_run"
+ , "target.after_test"
, "target.after_load"
, "target.after_link"
, "target.after_build"