diff --git a/.bazelrc b/.bazelrc index 1eb348b6cb4eca1abfbc6bd375c0a13910cf48d1..0a1bc1da54d7e6a7a372a6ff8c81ee6116183369 100644 --- a/.bazelrc +++ b/.bazelrc @@ -27,3 +27,5 @@ common --extra_toolchains=@rules_zstd//zstd/toolchain/zstd:built # User-specific .bazelrc try-import %workspace%/.bazelrc.user + +build --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 \ No newline at end of file diff --git a/.bazelrc.ci b/.bazelrc.ci index 2493d29127b8a89def55e1b97439b422f9bc8bde..4d385a4c83bd41582a719e508fb282aff2734b83 100644 --- a/.bazelrc.ci +++ b/.bazelrc.ci @@ -31,6 +31,7 @@ common --lockfile_mode=error # These locations are cached on the CI build:local --disk_cache=${CI_PROJECT_DIR}/.cache/bazel/disk build:local --repository_cache=${CI_PROJECT_DIR}/.cache/bazel/repo +build:local --action_env BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 # Use remote cache/execution for our builds build:remote --remote_executor=${CI_REMOTE_EXECUTOR} @@ -62,3 +63,4 @@ build:remote --nolegacy_important_outputs # Describe remote executors build:remote --extra_execution_platforms=@toolchain_utils//toolchain/platform:amd64-linux + diff --git a/MODULE.bazel b/MODULE.bazel index e6744440e518a7c62072250fae392e4623bd8e5c..714aef38e0fab9ff450a0c9f5f43a5b1f8255de9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -14,7 +14,8 @@ bazel_dep(name = "download_utils", version = "1.0.0-beta.2") bazel_dep(name = "rules_tar", version = "1.0.0-beta.3") bazel_dep(name = "rules_zstd", version = "1.0.0-beta.3") bazel_dep(name = "platforms", version = "0.0.10") - +bazel_dep(name = "rules_go", version = "0.49.0") +bazel_dep(name = "rules_cc", version = "0.0.9") bazel_dep(name = "hermetic_cc_toolchain", version = "3.1.0", dev_dependency = True) # Register LabGrid toolchains diff --git a/WORKSPACE b/WORKSPACE index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c6ea7fffc6c4d9c0a715a255a9364c31b648a251 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -0,0 +1,9 @@ +workspace(name = "Catch2") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "catch2", + strip_prefix = "Catch2-3.1.1", + urls = ["https://github.com/catchorg/Catch2/archive/v3.1.1.tar.gz"], +) diff --git a/e2e/docker/BUILD.bazel b/e2e/docker/BUILD.bazel index 4c49090016f7ed991345903e77320f4743e5fe7f..46106393f9551719e3e49a20f191b6b210b661aa 100644 --- a/e2e/docker/BUILD.bazel +++ b/e2e/docker/BUILD.bazel @@ -1,6 +1,7 @@ load("@rules_diff//diff/file/test:defs.bzl", "diff_file_test") load("@rules_labgrid//labgrid/config/toolchain:defs.bzl", "labgrid_config_toolchain") load("@rules_labgrid//labgrid/run:defs.bzl", "labgrid_run") +load("@rules_labgrid//labgrid/test:defs.bzl", "labgrid_test") load("@rules_python//python:defs.bzl", "py_binary") # A constraint to register the toolchain to @@ -21,6 +22,16 @@ platform( ], ) +# A platform that describes the Docker "platform" +platform( + name = "my_platform", + constraint_values = [ + ":constraint", + "@toolchain_utils//toolchain/constraint/os:linux", + "@platforms//cpu:x86_64", + ], +) + # Provide the toolchain with Bazel labgrid_config_toolchain( name = "config", @@ -57,3 +68,20 @@ diff_file_test( b = ":stdout.log", tags = ["manual"], ) + +# A hello world python binary +py_binary( + name = "hello", + srcs = ["hello.py"], +) + +labgrid_test( + name = "simple_test", + srcs = [":hello"], + binary = ":hello", + platform = ":my_platform", + tags = ["manual"], + tools = [ + ":hello", + ], +) diff --git a/e2e/docker/hello.py b/e2e/docker/hello.py new file mode 100644 index 0000000000000000000000000000000000000000..e75154b7c390fdc4aa85d86e0a191be255a00627 --- /dev/null +++ b/e2e/docker/hello.py @@ -0,0 +1 @@ +print("hello world") \ No newline at end of file diff --git a/labgrid/run/run.py b/labgrid/run/run.py index 44e157bb68c9a2c237ad7fe4f49a9e117a7fbb3f..24af54c46433e1cbce16c750c452a02651e2fa2f 100644 --- a/labgrid/run/run.py +++ b/labgrid/run/run.py @@ -1,5 +1,5 @@ #! /usr/bin/env python3 - +import time from argparse import ArgumentParser from os import environ, linesep from pathlib import Path, PurePath diff --git a/labgrid/test/BUILD.bazel b/labgrid/test/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..254c3528b5753dccc7c014a2d982f69838ec4efe --- /dev/null +++ b/labgrid/test/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +exports_files(["posix.tmpl.sh"]) + +py_binary( + name = "run", + srcs = ["run.py"], + tags = ["manual"], + visibility = ["//visibility:public"], + deps = [ + "//labgrid:pkg", + "//labgrid/config:deps", + ], +) \ No newline at end of file diff --git a/labgrid/test/defs.bzl b/labgrid/test/defs.bzl new file mode 100644 index 0000000000000000000000000000000000000000..6ad63a44708df7bb887e0c78b79b65f35a1fd5cc --- /dev/null +++ b/labgrid/test/defs.bzl @@ -0,0 +1,5 @@ +load(":rule.bzl", _test = "test") + +visibility("public") + +labgrid_test = _test diff --git a/labgrid/test/posix.tmpl.sh b/labgrid/test/posix.tmpl.sh new file mode 100644 index 0000000000000000000000000000000000000000..eb73f71337b13dbd8f437d09108554e3cbc463d2 --- /dev/null +++ b/labgrid/test/posix.tmpl.sh @@ -0,0 +1,78 @@ +#! /usr/bin/env sh + +# Strict shell +set -o errexit +set -o nounset + +# Substitutions +EXECUTOR="{{executor}}" +EXECUTOR_ARGS="{{executor_args}}" +RUN="${RUNFILES_DIR}/{{run}}" +TEST_RUNNER="${RUNFILES_DIR}/{{test_runner}}" +BINARY="${RUNFILES_DIR}/{{binary}}" +BINARY_ARGS="{{binary_args}}" + +readonly EXECUTOR EXECUTOR_ARGS RUN TEST_RUNNER BINARY BINARY_ARGS + +# Check the executor is executable +if ! test -e "${EXECUTOR}"; then + printf >&2 "Executor does not exist: %s\n" "${EXECUTOR}" + exit 1 +elif ! test -f "${EXECUTOR}"; then + printf >&2 "Executor not a file: %s\n" "${EXECUTOR}" + exit 1 +elif ! test -x "${EXECUTOR}"; then + printf >&2 "Executor is not executable: %s\n" "${EXECUTOR}" + exit 1 +fi + +# Check the Run.py is executable +if ! test -e "${RUN}"; then + printf >&2 "Run does not exist: %s\n" "${RUN}" + exit 1 +elif ! test -f "${RUN}"; then + printf >&2 "Run not a file: %s\n" "${RUN}" + exit 1 +elif ! test -x "${RUN}"; then + printf >&2 "Run is not executable: %s\n" "${RUN}" + exit 1 +fi + +# Check the test runner is executable +if ! test -e "${TEST_RUNNER}"; then + printf >&2 "Test Runner does not exist: %s\n" "${TEST_RUNNER}" + exit 1 +elif ! test -f "${TEST_RUNNER}"; then + printf >&2 "Test Runner not a file: %s\n" "${TEST_RUNNER}" + exit 1 +elif ! test -x "${TEST_RUNNER}"; then + printf >&2 "Test Runner is not executable: %s\n" "${TEST_RUNNER}" + exit 1 +fi + +# Check the binary is executable +if ! test -e "${BINARY}"; then + printf >&2 "Test Binary does not exist: %s\n" "${BINARY}" + exit 1 +elif ! test -f "${BINARY}"; then + printf >&2 "Test Binary not a file: %s\n" "${BINARY}" + exit 1 +elif ! test -x "${BINARY}"; then + printf >&2 "Test Binary is not executable: %s\n" "${BINARY}" + exit 1 +fi + +# Hardcoded EXECUTOR_ARGS for now till we can get the args file passed through correctly. +EXECUTOR_ARG_STRING="--manager ../rules_labgrid~/labgrid/manager/config.py --state accessible" +# EXECUTOR_ARG_STRING=@$EXECUTOR_ARGS +# Run the binary +printf >&2 "$EXECUTOR $EXECUTOR_ARG_STRING -- $RUN --test-runner=$TEST_RUNNER $BINARY ${@} \n\n" +$EXECUTOR $EXECUTOR_ARG_STRING -- $RUN --test-runner=$TEST_RUNNER $BINARY ${@} +# Write the output to XML_OUTPUT_FILE +echo "XML_OUTPUT_FILE" +echo "$XML_OUTPUT_FILE" +[ -f /tmp/output.xml ] && cp -f /tmp/output.xml "${XML_OUTPUT_FILE}" +#cat "${XML_OUTPUT_FILE}" + + +echo "$RUNFILES_DIR" diff --git a/labgrid/test/rule.bzl b/labgrid/test/rule.bzl new file mode 100644 index 0000000000000000000000000000000000000000..e680d216fccbe4a1c7ca1ff7d89562bc64e43bbe --- /dev/null +++ b/labgrid/test/rule.bzl @@ -0,0 +1,131 @@ +load("@bazel_skylib//lib:shell.bzl", "shell") +load("//labgrid/cfg:store.bzl", _cfg = "store") + +visibility("//...") + +DOC = """Executes a binary on the device within a LabGrid environment. + +The "@rules_labgrid//labgrid/toolchain/executor:type` toolchain is used to create the environment that the command runs under. + +""" + +ATTRS = { + "binary": attr.label( + doc = "A target to run on the device as the test binary", + mandatory = True, + cfg = "target", + executable = True, + ), + "srcs": attr.label_list( + doc = "A list of inputs for this rule, such as files to process.", + cfg = "target", + allow_files = True, + ), + "tools": attr.label_list( + doc = "A list of tools for this rule, that will run on the executor.", + cfg = "exec", + ), + # "out": attr.output( + # doc = "Output generated by this rule.", + # ), + "platform": attr.label( + doc = "Platform to transition to.", + providers = [platform_common.PlatformInfo], + ), + "_executor": attr.label( + doc = "The LabGrid environment executor", + default = "//labgrid/executor", + executable = True, + cfg = "exec", + ), + "_executor_args": attr.label( + allow_single_file = True, + doc = "CLI args for the Labgrid environment executor", + default = "//labgrid/executor:args", + cfg = "exec", + ), + "_template": attr.label( + doc = "The launcher script.", + allow_single_file = [".sh"], + default = ":posix.tmpl.sh", + cfg = "exec", + ), + "_test_runner": attr.label( + doc = "The test_runner", + default = "//labgrid/test_runner:runner", + cfg = "target", + executable = True, + ), + "_run": attr.label( + doc = "The run binary", + default = "//labgrid/test:run", + cfg = "exec", + executable = True, + ), +} + +# This piece of code is from +# https://github.com/bazel-contrib/bazel-lib/blob/f0e8c006c0c75c832b7d461ffd5262ec8e3ddca4/lib/private/paths.bzl#L79-L111 +def _to_rlocation_path(ctx, file): + if file.short_path.startswith("../"): + return file.short_path[3:] + else: + return ctx.workspace_name + "/" + file.short_path + +def implementation(ctx): + runner = ctx.attr._test_runner + run = ctx.attr._run + binary = ctx.attr.binary + + rendered = ctx.actions.declare_file("{}/test.{}".format(ctx.label.name, ctx.file._template.extension)) + + # Perform rlocation lookup on the executables. + relative_test_runner_path = _to_rlocation_path(ctx, ctx.attr._test_runner[DefaultInfo].files_to_run.executable) + relative_run_path = _to_rlocation_path(ctx, ctx.attr._run[DefaultInfo].files_to_run.executable) + relative_binary_path = _to_rlocation_path(ctx, ctx.attr.binary[DefaultInfo].files_to_run.executable) + + substitutions = ctx.actions.template_dict() + substitutions.add("{{executor}}", ctx.executable._executor.short_path) + substitutions.add("{{executor_args}}", ctx.file._executor_args.short_path) + substitutions.add("{{run}}", relative_run_path) + substitutions.add("{{test_runner}}", relative_test_runner_path) + substitutions.add("{{binary}}", relative_binary_path) + substitutions.add("{{binary_args}}", str("")) + + ctx.actions.expand_template( + template = ctx.file._template, + output = rendered, + computed_substitutions = substitutions, + is_executable = True, + ) + + transitive_runfiles = [] + transitive_runfiles.append(ctx.attr._executor[DefaultInfo].default_runfiles) + transitive_runfiles.append(ctx.attr._executor_args[DefaultInfo].default_runfiles) + transitive_runfiles.append(run[DefaultInfo].default_runfiles) + transitive_runfiles.append(runner[DefaultInfo].default_runfiles) + transitive_runfiles.append(binary[DefaultInfo].default_runfiles) + + for target in ctx.attr.tools: + transitive_runfiles.append(target[DefaultInfo].default_runfiles) + + for target in ctx.attr.srcs: + transitive_runfiles.append(target[DefaultInfo].default_runfiles) + + runfiles = ctx.runfiles([rendered]) + runfiles = runfiles.merge_all(transitive_runfiles) + + return DefaultInfo( + executable = rendered, + runfiles = runfiles, + ) + +labgrid_test = rule( + doc = DOC, + attrs = ATTRS, + implementation = implementation, + cfg = _cfg, + test = True, +) + +test = labgrid_test diff --git a/labgrid/test/run.py b/labgrid/test/run.py new file mode 100644 index 0000000000000000000000000000000000000000..bdaf6625d72f0abaabfd9eef70c85bc4c6bbeb74 --- /dev/null +++ b/labgrid/test/run.py @@ -0,0 +1,120 @@ +#! /usr/bin/env python3 +import time +from argparse import ArgumentParser +from os import environ, linesep +from pathlib import Path, PurePath +from shlex import join +from sys import argv, stderr, stdout +from typing import Collection, Protocol, Tuple +import os +from labgrid import Environment + + +class Shell(Protocol): + def run(cmd: str) -> Tuple[Collection[str], Collection[str], int]: ... + + +def arguments(prsr: ArgumentParser) -> None: + prsr.add_argument( + "program", + metavar="PROG", + help="The program to run on the device.", + type=Path, + ) + prsr.add_argument( + "arguments", metavar="ARG", nargs="*", help="Arguments to the program" + ) + prsr.add_argument( + "--test-runner", + help="The test runner for the device.", + type=Path, + ) + prsr.add_argument( + "--test-runner-args", + help="Arguments for the test runner", + nargs="*", + default=[], + ) + prsr.add_argument( + "--config", + help="The LabGrid configuration.", + default=PurePath(environ["LG_ENV"]), + type=PurePath, + ) + prsr.add_argument( + "--state", + help="The state to transition the LabGrid strategy to", + default=environ["LG_STATE"], + ) + + +def main(exe: Path, *args: str) -> int: + prsr = ArgumentParser( + prog=str(exe), description="Runs a command over SSH to a LabGrid Docker device." + ) + + arguments(prsr) + + parsed = prsr.parse_args(args) + + # Start up Docker container with LabGrid + env = Environment(str(parsed.config)) + target = env.get_target() + strategy = target.get_driver("Strategy") + strategy.transition(parsed.state) + + # Retrieve the communication protocols + shell = target.get_driver("CommandProtocol") + transfer = target.get_driver("FileTransferProtocol") + + + # Transfer the provided program and test runner over to the Docker image + # TODO: this should be a context manager that removes it later + # TODO: this should use a unique remote name/directory + # Transfer runfiles + src_runfiles = os.getenv("RUNFILES_DIR","") + dest_runfiles = "/tmp/" + transfer.put(src_runfiles, dest_runfiles) + test_runner = "/tmp/{}/{}".format(os.path.basename(src_runfiles),parsed.test_runner.name) + transfer.put(parsed.test_runner, test_runner) + program = "/tmp/{}/{}".format(os.path.basename(src_runfiles),parsed.program.name) + transfer.put(parsed.program, program) + + + + + # Run the transferred program in the test_runner + + # cmd with binary_args which isn't supported yet in the runner. + # args = join(*parsed.arguments) + # test_runner_cmd = f"{join(test_runner, *parsed.test_runner_args)} -binary={program} -binary_args={args}" + stdout.write(f"run.py: just about to run the test runner\n") + + test_runner_cmd = f"{test_runner} -binary={program}" + stdout.write(f"run.py: {test_runner_cmd}\n") + xml_path = "output.xml" + out, err, code = shell.run(f"XML_OUTPUT_FILE={xml_path} {test_runner_cmd}") + #out, err, code = shell.run(test_runner_cmd) + for line in out: + stdout.write(f"{line}{linesep}") + for line in err: + stderr.write(f"{line}{linesep}") + #time.sleep(5000) + stdout.write(f"run.py: finished running the test runner\n") + stdout.write(f"run.py: return code {code}") + if code == 0 : + try:#TODO remove the try catch - and check if the file exist + transfer.get(f"/tmp/{os.path.basename(src_runfiles)}/{xml_path}","/tmp/output.xml") + with open("/tmp/output.xml","r") as f: + print(f.readlines()) + except: + print("error getting file") + return code + + +def entry(): + exit(main(Path(argv[0]), *argv[1:])) + + +if __name__ == "__main__": + entry() diff --git a/labgrid/test_runner/BUILD.bazel b/labgrid/test_runner/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..bfb38b6378633873adc8bae0bd68564e52165b00 --- /dev/null +++ b/labgrid/test_runner/BUILD.bazel @@ -0,0 +1,37 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("@rules_go//go:def.bzl", "go_binary") +load("@rules_python//python:defs.bzl", "py_test") + +cc_library( + name = "my_lib", + srcs = ["my_lib.cpp"], + hdrs = ["my_lib.h"], +) + +cc_binary( + name = "test_binary", + srcs = ["test_binary.cpp"], + deps = [ + ":my_lib", + "@catch2//:catch2_main", + ], +) + +go_binary( + name = "runner", + srcs = ["main.go"], + # data = [":test_binary"], + env = {"XML_OUTPUT_FILE": "/your_xml_output_path/"}, #when using bazel test - bazel will ignore it + pure = "on", + visibility = ["//visibility:public"], +) + +py_test( + name = "some-test", + srcs = ["some-test.py"], + data = [ + ":runner", + ":test_binary", + ], + deps = ["@rules_python//python/runfiles"], +) diff --git a/labgrid/test_runner/main.go b/labgrid/test_runner/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d6988fa7a9dd6988ef2db53d5041c204a40b1d1c --- /dev/null +++ b/labgrid/test_runner/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" +) + +func main() { + var redirToFile bool + var binary_path string + flag.StringVar(&binary_path, "binary", "", "binary path") + flag.BoolVar(&redirToFile, "redir", false, "redirect output to XML_OUTPUT_FILE") + flag.Parse() + + fmt.Printf("binary path is: %s\n", binary_path) + binary_abs_path, _ := filepath.Abs(binary_path) + fmt.Printf("absolute binary path is: %s\n", binary_abs_path) + + runner_exe_path, err := os.Executable() + if err != nil { + panic(err) + } + + runner_path := filepath.Dir(runner_exe_path) + xmlOutputPath := os.Getenv("XML_OUTPUT_FILE") //the default filename of bazel XML_OUTPUT_FILE is test.xml + if xmlOutputPath == "" { + fmt.Println("XML_OUTPUT_FILE was empty - exit") + os.Exit(1) + } else if !filepath.IsAbs(xmlOutputPath) { + fmt.Println("XML_OUTPUT_FILE was relative") + xmlOutputPath = filepath.Join(runner_path, xmlOutputPath) + } + cmd := exec.Command(binary_abs_path) + if redirToFile { + cmd = exec.Command("sh", "-c", binary_abs_path, fmt.Sprintf(" > %s", xmlOutputPath)) + } else { + cmd = exec.Command(binary_abs_path) + } + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("XML_OUTPUT_FILE=%s", xmlOutputPath)) + //cmd = exec.Command("sh", "-c", fmt.Sprintf("printenv > %s", xmlOutputPath)) //remove it - only for debug + out, err := cmd.Output() + fmt.Printf("Command Output is:\n %s\n", out) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/labgrid/test_runner/my_lib.cpp b/labgrid/test_runner/my_lib.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c2b164bfcc8d328dc74fbfd0e58b0e5d7ecd8bf4 --- /dev/null +++ b/labgrid/test_runner/my_lib.cpp @@ -0,0 +1,11 @@ +#include "my_lib.h" + +int add(int a, int b) +{ + return a + b; +} + +int multiply(int a, int b) +{ + return a * b; +} \ No newline at end of file diff --git a/labgrid/test_runner/my_lib.h b/labgrid/test_runner/my_lib.h new file mode 100644 index 0000000000000000000000000000000000000000..da55205aa70f814d31436a798dda01f06b88ed0b --- /dev/null +++ b/labgrid/test_runner/my_lib.h @@ -0,0 +1,2 @@ +int add(int a, int b); +int multiply(int a, int b); \ No newline at end of file diff --git a/labgrid/test_runner/some-test.py b/labgrid/test_runner/some-test.py new file mode 100644 index 0000000000000000000000000000000000000000..9e63e59f768cf5b4a296a3d8fa0c3144263122b4 --- /dev/null +++ b/labgrid/test_runner/some-test.py @@ -0,0 +1,29 @@ +import subprocess +import unittest +from python.runfiles import Runfiles +import os +import xml.etree.ElementTree as et + +class TestGoBinary(unittest.TestCase): + def test_go_binary(self): + runfiles = Runfiles.Create() + go_binary = runfiles.Rlocation("rules_labgrid/labgrid/test_runner/runner_/runner") + test_binary = runfiles.Rlocation("rules_labgrid/labgrid/test_runner/test_binary") + result = subprocess.run( + [go_binary,f"-binary={test_binary}"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + self.assertEqual(result.returncode,0,"error") + xml_output_file = os.getenv("XML_OUTPUT_FILE","not-found") + with open(xml_output_file,"r") as f: + output_real_data = f.read() + print(output_real_data) + try: + et.parse(xml_output_file) + self.assertTrue(True) + except Exception: + self.assertTrue(False,"failed to parse xml file") + +if __name__ =="__main__": + unittest.main() diff --git a/labgrid/test_runner/test_binary.cpp b/labgrid/test_runner/test_binary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d57045640011ba61b6214b242b91aaf0f373b2b0 --- /dev/null +++ b/labgrid/test_runner/test_binary.cpp @@ -0,0 +1,12 @@ +#define CATCH_CONFIG_MAIN +#include +#include "my_lib.h" + +TEST_CASE("Addition", "[add]") +{ + REQUIRE(add(2, 3) == 5); +} +TEST_CASE("Multiplication", "[multiply]") +{ + REQUIRE(multiply(2, 5) == 10); +} \ No newline at end of file