diff options
author | Klaus Aehlig <klaus.aehlig@huawei.com> | 2023-02-23 12:02:57 +0100 |
---|---|---|
committer | Alberto Sartori <alberto.sartori@huawei.com> | 2023-03-10 09:38:39 +0100 |
commit | d438cc5f81f1a13c6fffd874be7e72201a7b65bd (patch) | |
tree | ee5822d5bcddf4d5eb9088ee4f5e0748528ebb8c /test | |
parent | 8408b27d3ab64d8f95aff3c17b9dcc4c69a7cac5 (diff) | |
download | justbuild-d438cc5f81f1a13c6fffd874be7e72201a7b65bd.tar.gz |
Add a rule for CC tests depending on remote execution
In order to keep our tests self-contained, do not rely on
an external remote-execution service to be present; instead,
use `just execute` to provide the remote execution service.
Diffstat (limited to 'test')
-rw-r--r-- | test/utils/remote_execution/RULES | 335 | ||||
-rw-r--r-- | test/utils/remote_execution/TARGETS | 1 | ||||
-rwxr-xr-x | test/utils/remote_execution/test_runner.py | 126 |
3 files changed, 462 insertions, 0 deletions
diff --git a/test/utils/remote_execution/RULES b/test/utils/remote_execution/RULES new file mode 100644 index 00000000..340fc83b --- /dev/null +++ b/test/utils/remote_execution/RULES @@ -0,0 +1,335 @@ +{ "CC test": + { "doc": ["A test written in C++, depending on remote execution"] + , "tainted": ["test"] + , "target_fields": ["srcs", "private-hdrs", "private-deps", "data"] + , "string_fields": + [ "name" + , "args" + , "stage" + , "pure C" + , "private-defines" + , "private-cflags" + , "private-ldflags" + , "compatible remote" + ] + , "config_vars": + [ "ARCH" + , "HOST_ARCH" + , "CC" + , "CXX" + , "CFLAGS" + , "CXXFLAGS" + , "ADD_CFLAGS" + , "ADD_CXXFLAGS" + , "ENV" + , "TEST_ENV" + , "CC_TEST_LAUNCHER" + , "TEST_COMPATIBLE_REMOTE" + ] + , "implicit": + { "defaults": [["@", "rules", "CC", "defaults"]] + , "runner": ["test_runner.py"] + , "just": [["", "just"]] + } + , "field_doc": + { "name": + [ "The name of the test" + , "" + , "Used to name the test binary as well as for staging the test result" + ] + , "args": ["Command line arguments for the test binary"] + , "srcs": ["The sources of the test binary"] + , "private-hdrs": + [ "Any additional header files that need to be present when compiling" + , "the test binary." + ] + , "private-defines": + [ "List of defines set for source files local to this target." + , "Each list entry will be prepended by \"-D\"." + ] + , "private-cflags": + ["List of compile flags set for source files local to this target."] + , "private-ldflags": + [ "Additional linker flags for linking external libraries (not built" + , "by this tool, typically system libraries)." + ] + , "stage": + [ "The logical location of all header and source files." + , "Individual directory components are joined with \"/\"." + ] + , "pure C": + [ "If non-empty, compile as C sources rathter than C++ sources." + , "In particular, CC is used to compile rather than CXX (or their" + , "respective defaults)." + ] + , "data": ["Any files the test binary needs access to when running"] + } + , "config_doc": + { "CC": ["The name of the C compiler to be used."] + , "CXX": ["The name of the C++ compiler to be used."] + , "CFLAGS": + [ "The flags for CC to be used instead of the default ones" + , "taken from the [\"CC\", \"defaults\"] target" + ] + , "CXXFLAGS": + [ "The flags for CXX to be used instead of the default ones" + , "taken from the [\"CC\", \"defaults\"] target" + ] + , "ADD_CFLAGS": + [ "The flags to add to the default ones for CC" + , "taken from the [\"CC\", \"defaults\"] target" + ] + , "ADD_CXXFLAGS": + [ "The flags to add to the default ones for CXX" + , "taken from the [\"CC\", \"defaults\"] target" + ] + , "ENV": ["The environment for any action generated."] + , "TEST_ENV": ["The environment for executing the test runner."] + , "CC_TEST_LAUNCHER": + [ "List of strings representing the launcher that is prepend to the" + , "command line for running the test binary." + ] + , "TEST_COMPATIBLE_REMOTE": + [ "If true, provide compatible remote execution and set COMPATIBLE in the" + , " test environment; otherwise, provide a native remote execution" + , "do not modify the environment" + ] + } + , "artifacts_doc": + [ "result: the result of this test (\"PASS\" or \"FAIL\"); useful for" + , " generating test reports." + , "stdout/stderr: Any output the invocation of the test binary produced on" + , " the respective file descriptor" + , "time-start/time-stop: The time (decimally coded) in seconds since the" + , " epoch when the test invocation started and ended." + ] + , "runfiles_doc": + [ "A tree consisting of the artifacts staged at the name of the test." + , "As the built-in \"install\" rule only takes the runfiles of its" + , "\"private-deps\" argument, this gives an easy way of defining test" + , "suites." + ] + , "imports": + { "artifacts": ["@", "rules", "", "field_artifacts"] + , "runfiles": ["@", "rules", "", "field_runfiles"] + , "compile-deps": ["@", "rules", "CC", "compile-deps"] + , "compile-args-deps": ["@", "rules", "CC", "compile-args-deps"] + , "link-deps": ["@", "rules", "CC", "link-deps"] + , "link-args-deps": ["@", "rules", "CC", "link-args-deps"] + , "binary": ["@", "rules", "CC", "bin artifact"] + , "host transition": ["@", "rules", "transitions", "for host"] + , "stage": ["@", "rules", "", "stage_singleton_field"] + } + , "config_transitions": + { "defaults": [{"type": "CALL_EXPRESSION", "name": "host transition"}] + , "private-deps": [{"type": "CALL_EXPRESSION", "name": "host transition"}] + , "data": [{"type": "CALL_EXPRESSION", "name": "host transition"}] + , "just": [{"type": "CALL_EXPRESSION", "name": "host transition"}] + , "runner": [{"type": "CALL_EXPRESSION", "name": "host transition"}] + } + , "expression": + { "type": "let*" + , "bindings": + [ [ "name" + , { "type": "assert_non_empty" + , "msg": "A non-empty name has to be provided for binaries" + , "$1": {"type": "join", "$1": {"type": "FIELD", "name": "name"}} + } + ] + , ["pure C", {"type": "FIELD", "name": "pure C"}] + , [ "stage" + , { "type": "join" + , "separator": "/" + , "$1": {"type": "FIELD", "name": "stage"} + } + ] + , [ "srcs" + , { "type": "to_subdir" + , "subdir": {"type": "var", "name": "stage"} + , "$1": + { "type": "let*" + , "bindings": [["fieldname", "srcs"]] + , "body": {"type": "CALL_EXPRESSION", "name": "artifacts"} + } + } + ] + , [ "private-hdrs" + , { "type": "to_subdir" + , "subdir": {"type": "var", "name": "stage"} + , "$1": + { "type": "let*" + , "bindings": [["fieldname", "private-hdrs"]] + , "body": {"type": "CALL_EXPRESSION", "name": "artifacts"} + } + } + ] + , ["host-trans", {"type": "CALL_EXPRESSION", "name": "host transition"}] + , ["defaults-transition", {"type": "var", "name": "host-trans"}] + , ["deps-transition", {"type": "var", "name": "host-trans"}] + , ["deps-fieldnames", ["private-deps"]] + , ["compile-deps", {"type": "CALL_EXPRESSION", "name": "compile-deps"}] + , [ "compile-args" + , { "type": "++" + , "$1": + [ { "type": "foreach" + , "var": "def" + , "range": {"type": "FIELD", "name": "private-defines"} + , "body": + {"type": "join", "$1": ["-D", {"type": "var", "name": "def"}]} + } + , {"type": "FIELD", "name": "private-cflags"} + , {"type": "CALL_EXPRESSION", "name": "compile-args-deps"} + ] + } + ] + , ["link-deps", {"type": "CALL_EXPRESSION", "name": "link-deps"}] + , [ "link-args" + , { "type": "++" + , "$1": + [ {"type": "CALL_EXPRESSION", "name": "link-args-deps"} + , {"type": "FIELD", "name": "private-ldflags"} + ] + } + ] + , ["binary", {"type": "CALL_EXPRESSION", "name": "binary"}] + , [ "staged test binary" + , { "type": "map_union" + , "$1": + { "type": "foreach_map" + , "range": {"type": "var", "name": "binary"} + , "var_val": "binary" + , "body": + { "type": "singleton_map" + , "key": "test" + , "value": {"type": "var", "name": "binary"} + } + } + } + ] + , ["transition", {"type": "CALL_EXPRESSION", "name": "host transition"}] + , ["fieldname", "runner"] + , ["location", "runner"] + , ["runner", {"type": "CALL_EXPRESSION", "name": "stage"}] + , ["fieldname", "just"] + , ["location", "just"] + , ["just", {"type": "CALL_EXPRESSION", "name": "stage"}] + , [ "test-args" + , { "type": "singleton_map" + , "key": "test-args.json" + , "value": + { "type": "BLOB" + , "data": + { "type": "json_encode" + , "$1": {"type": "FIELD", "name": "args", "default": []} + } + } + } + ] + , [ "compatible-remote" + , { "type": "singleton_map" + , "key": "compatible-remote.json" + , "value": + { "type": "BLOB" + , "data": + { "type": "if" + , "cond": {"type": "var", "name": "TEST_COMPATIBLE_REMOTE"} + , "then": "true" + , "else": "false" + } + } + } + ] + , [ "test-launcher" + , { "type": "singleton_map" + , "key": "test-launcher.json" + , "value": + { "type": "BLOB" + , "data": + { "type": "json_encode" + , "$1": + {"type": "var", "name": "CC_TEST_LAUNCHER", "default": []} + } + } + } + ] + , [ "data" + , { "type": "let*" + , "bindings": + [ ["fieldname", "data"] + , ["transition", {"type": "var", "name": "deps-transition"}] + ] + , "body": + { "type": "map_union" + , "$1": + [ {"type": "CALL_EXPRESSION", "name": "runfiles"} + , {"type": "CALL_EXPRESSION", "name": "artifacts"} + ] + } + } + ] + , [ "test-name" + , { "type": "join" + , "separator": "/" + , "$1": + [{"type": "var", "name": "stage"}, {"type": "var", "name": "name"}] + } + ] + , [ "test-results" + , { "type": "ACTION" + , "outs": + [ "result" + , "stdout" + , "stderr" + , "time-start" + , "time-stop" + , "remotestdout" + , "remotestderr" + ] + , "inputs": + { "type": "map_union" + , "$1": + [ { "type": "to_subdir" + , "subdir": "work" + , "$1": {"type": "var", "name": "data"} + } + , {"type": "var", "name": "just"} + , {"type": "var", "name": "compatible-remote"} + , {"type": "var", "name": "runner"} + , {"type": "var", "name": "test-args"} + , {"type": "var", "name": "test-launcher"} + , {"type": "var", "name": "staged test binary"} + ] + } + , "cmd": ["./runner"] + , "env": + { "type": "var" + , "name": "TEST_ENV" + , "default": {"type": "empty_map"} + } + , "may_fail": ["test"] + , "fail_message": + { "type": "join" + , "$1": + [ "remote execution CC test " + , {"type": "var", "name": "test-name"} + , " failed" + ] + } + } + ] + , [ "runfiles" + , { "type": "singleton_map" + , "key": {"type": "var", "name": "name"} + , "value": + {"type": "TREE", "$1": {"type": "var", "name": "test-results"}} + } + ] + ] + , "body": + { "type": "RESULT" + , "artifacts": {"type": "var", "name": "test-results"} + , "runfiles": {"type": "var", "name": "runfiles"} + } + } + } +} diff --git a/test/utils/remote_execution/TARGETS b/test/utils/remote_execution/TARGETS new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/test/utils/remote_execution/TARGETS @@ -0,0 +1 @@ +{} diff --git a/test/utils/remote_execution/test_runner.py b/test/utils/remote_execution/test_runner.py new file mode 100755 index 00000000..b77125be --- /dev/null +++ b/test/utils/remote_execution/test_runner.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. +# +# 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. + +import json +import os +import subprocess +import time + +time_start = time.time() +time_stop = 0 +result = "UNKNOWN" +stderr = "" +stdout = "" +remotestdout = "" +remotestderr = "" + + +def dump_results(): + with open("result", "w") as f: + f.write("%s\n" % (result, )) + with open("time-start", "w") as f: + f.write("%d\n" % (time_start, )) + with open("time-stop", "w") as f: + f.write("%d\n" % (time_stop, )) + with open("stdout", "w") as f: + f.write("%s\n" % (stdout, )) + with open("stderr", "w") as f: + f.write("%s\n" % (stderr, )) + with open("remotestdout", "w") as f: + f.write("%s\n" % (remotestdout, )) + with open("remotestderr", "w") as f: + f.write("%s\n" % (remotestderr, )) + + +dump_results() + +TEMP_DIR = os.path.realpath("scratch") +os.makedirs(TEMP_DIR, exist_ok=True) + +WORK_DIR = os.path.realpath("work") +os.makedirs(WORK_DIR, exist_ok=True) + +REMOTE_DIR = os.path.realpath("remote") +os.makedirs(REMOTE_DIR, exist_ok=True) + +REMOTE_INFO = os.path.join(REMOTE_DIR, "info.json") +REMOTE_LBR = os.path.join(REMOTE_DIR, "build-root") + +remote_cmd = [ + "./just", "execute", + "--info-file", REMOTE_INFO, + "--local-build-root", REMOTE_LBR, + "--log-limit", "5", "--plain-log", +] + +with open("compatible-remote.json") as f: + compatible = json.load(f) + +if compatible: + remote_cmd.append("--compatible") + +remote_proc = subprocess.Popen( + remote_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, +) + +while not os.path.exists(REMOTE_INFO): + time.sleep(1) + +with open(REMOTE_INFO) as f: + info = json.load(f) + +REMOTE_EXECUTION_ADDRESS = "%s:%d" % (info["interface"], info["port"]) + +ENV = dict(os.environ, + TEST_TMPDIR=TEMP_DIR, + TMPDIR=TEMP_DIR, + REMOTE_EXECUTION_ADDRESS=REMOTE_EXECUTION_ADDRESS) + +if compatible: + ENV["COMPATIBLE"] = "YES" +elif "COMPATIBLE" in ENV: + del ENV["COMPATIBLE"] + +for k in ["TLS_CA_CERT", "TLS_CLIENT_CERT", "TLS_CLIENT_KEY"]: + if k in ENV: + del ENV[k] + +with open('test-launcher.json') as f: + test_launcher = json.load(f) + +with open('test-args.json') as f: + test_args = json.load(f) + +time_start = time.time() + +ret = subprocess.run(test_launcher + ["../test"] + test_args, + cwd=WORK_DIR, + env=ENV, + capture_output=True) + +time_stop = time.time() +result = "PASS" if ret.returncode == 0 else "FAIL" +stdout = ret.stdout.decode("utf-8") +stderr = ret.stderr.decode("utf-8") +remote_proc.terminate() +rout, rerr = remote_proc.communicate() +remotestdout = rout.decode("utf-8") +remotestderr = rerr.decode("utf-8") + +dump_results() + +if result != "PASS": exit(1) |