aboutsummaryrefslogtreecommitdiff
path: root/tools/interface_generator
diff options
context:
space:
mode:
Diffstat (limited to 'tools/interface_generator')
-rw-r--r--tools/interface_generator/.bazelrc2
-rw-r--r--tools/interface_generator/BUILD.bazel13
-rw-r--r--tools/interface_generator/LICENSE.md16
-rw-r--r--tools/interface_generator/MODULE.bazel20
-rw-r--r--tools/interface_generator/bin/BUILD.bazel45
-rw-r--r--tools/interface_generator/bin/interface_declaration.py58
-rw-r--r--tools/interface_generator/bin/interface_declaration_unittest.py19
-rwxr-xr-xtools/interface_generator/bin/interface_generator.py86
-rw-r--r--tools/interface_generator/bin/templates.py19
-rw-r--r--tools/interface_generator/bin/templates/__c_functions.mako4
-rw-r--r--tools/interface_generator/bin/templates/__c_system_include.mako4
-rw-r--r--tools/interface_generator/bin/templates/__c_types.mako6
-rw-r--r--tools/interface_generator/bin/templates/__header.mako5
-rw-r--r--tools/interface_generator/bin/templates/interface.h.mako8
-rw-r--r--tools/interface_generator/bin/templates/interface_mock.cpp.mako32
-rw-r--r--tools/interface_generator/bin/templates/interface_mock.hpp.mako26
-rw-r--r--tools/interface_generator/bin/templates_unittest.py30
-rw-r--r--tools/interface_generator/defs.bzl53
-rw-r--r--tools/interface_generator/private/BUILD.bazel9
-rw-r--r--tools/interface_generator/private/defs.bzl29
-rw-r--r--tools/interface_generator/private/mypyrc2
-rw-r--r--tools/interface_generator/private/pylintrc0
-rwxr-xr-xtools/interface_generator/private/pytest_wrapper.py7
-rw-r--r--tools/interface_generator/requirements.txt6
-rw-r--r--tools/interface_generator/requirements_lock.txt220
-rw-r--r--tools/interface_generator/test/BUILD.bazel24
-rw-r--r--tools/interface_generator/test/test_kstdio.cpp20
27 files changed, 763 insertions, 0 deletions
diff --git a/tools/interface_generator/.bazelrc b/tools/interface_generator/.bazelrc
new file mode 100644
index 0000000..71d2e60
--- /dev/null
+++ b/tools/interface_generator/.bazelrc
@@ -0,0 +1,2 @@
+build --cxxopt=-std=c++20
+
diff --git a/tools/interface_generator/BUILD.bazel b/tools/interface_generator/BUILD.bazel
new file mode 100644
index 0000000..8c4bde6
--- /dev/null
+++ b/tools/interface_generator/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+package(default_visibility = ["//visibility:public"])
+
+# This rule adds a convenient way to update the requirements file.
+compile_pip_requirements(
+ name = "requirements",
+ src = "requirements.txt",
+ requirements_txt = "requirements_lock.txt",
+)
+
+# make license available to test package
+exports_files(["LICENSE.md"])
diff --git a/tools/interface_generator/LICENSE.md b/tools/interface_generator/LICENSE.md
new file mode 100644
index 0000000..ddb4966
--- /dev/null
+++ b/tools/interface_generator/LICENSE.md
@@ -0,0 +1,16 @@
+Copyright (c) 2024 aqua@iserlohn-fortress.net
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+ OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ PERFORMANCE OF THIS SOFTWARE.
+
+SPDX License Identifier: [ISC](https://spdx.org/licenses/ISC.html)
diff --git a/tools/interface_generator/MODULE.bazel b/tools/interface_generator/MODULE.bazel
new file mode 100644
index 0000000..53b2ba4
--- /dev/null
+++ b/tools/interface_generator/MODULE.bazel
@@ -0,0 +1,20 @@
+###############################################################################
+# Bazel now uses Bzlmod by default to manage external dependencies.
+# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
+#
+# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
+###############################################################################
+
+module(name = "interface_generator", version = "0.1.0")
+
+# external dependencies
+bazel_dep(name = "googletest", version = "1.15.2")
+bazel_dep(name = "rules_python", version = "0.34.0")
+
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
+pip.parse(
+ hub_name = "pip",
+ python_version = "3.11",
+ requirements_lock = "//:requirements_lock.txt",
+)
+use_repo(pip, "pip")
diff --git a/tools/interface_generator/bin/BUILD.bazel b/tools/interface_generator/bin/BUILD.bazel
new file mode 100644
index 0000000..04843b9
--- /dev/null
+++ b/tools/interface_generator/bin/BUILD.bazel
@@ -0,0 +1,45 @@
+load("@pip//:requirements.bzl", "requirement")
+load("//private:defs.bzl", "py_pytest")
+
+""" interface declaration """
+
+py_library(
+ name = "interface_declaration",
+ srcs = ["interface_declaration.py"],
+)
+
+""" templates """
+
+py_library(
+ name = "templates",
+ srcs = ["templates.py"],
+ data = glob(["templates/*"]),
+)
+
+""" interface_generator """
+
+py_binary(
+ name = "interface_generator",
+ srcs = [
+ "interface_generator.py",
+ "templates.py",
+ ],
+ imports = ["."],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":interface_declaration",
+ ":templates",
+ requirement("mako"),
+ ],
+)
+
+""" pytest """
+
+py_pytest(
+ name = "pytest",
+ srcs = glob(["*.py"]),
+ data = glob(["templates/*"]),
+ deps = [
+ requirement("mako"),
+ ],
+)
diff --git a/tools/interface_generator/bin/interface_declaration.py b/tools/interface_generator/bin/interface_declaration.py
new file mode 100644
index 0000000..4560bbd
--- /dev/null
+++ b/tools/interface_generator/bin/interface_declaration.py
@@ -0,0 +1,58 @@
+"""
+interface_declaration.py
+"""
+
+from dataclasses import dataclass, asdict
+from pathlib import Path
+
+
+@dataclass
+class InterfaceDeclaration:
+ """interface declaration class"""
+
+ name: str
+ license_hdr: str
+ system_includes: list[str]
+ types: list[dict]
+ functions: list[dict]
+
+ def read_license(self, path: Path):
+ """read and starrify a license"""
+ if path is None:
+ self.license_hdr = ""
+ return
+
+ with open(path, encoding="utf-8") as license_file:
+ self.license_hdr = "".join(
+ [
+ f" * { line.rstrip().ljust(72)[:72] } * \n"
+ for line in license_file.readlines()
+ ]
+ ).rstrip()
+
+ def __init__(self, name: str, license_path: Path):
+ self.name = name if name is not None else "kstdio"
+ self.read_license(license_path)
+ self.system_includes = ["stdarg.h"]
+ self.types = [
+ {
+ "name": "File",
+ "members": [
+ "int fd",
+ "int (*putc)(const struct kstdio_File*, const char)",
+ "int (*puts)(const struct kstdio_File*, const char*)",
+ ],
+ },
+ ]
+ self.functions = [
+ {
+ "name": "printf",
+ "return": "int",
+ "arguments": ["const char* format"],
+ "argument_names": ["format"],
+ },
+ ]
+
+ def into_dict(self) -> dict:
+ """create a dictionary for use in mako"""
+ return asdict(self)
diff --git a/tools/interface_generator/bin/interface_declaration_unittest.py b/tools/interface_generator/bin/interface_declaration_unittest.py
new file mode 100644
index 0000000..b2ad80c
--- /dev/null
+++ b/tools/interface_generator/bin/interface_declaration_unittest.py
@@ -0,0 +1,19 @@
+"""
+interface declaration unit tests
+"""
+
+import unittest
+from interface_declaration import InterfaceDeclaration
+
+
+class InterfaceDeclarationUnittest(unittest.TestCase):
+ """interface_declaration unit tests"""
+
+ def test_interfacedeclaration_class_asdict(self):
+ """test mock interface"""
+ interface = InterfaceDeclaration("kstdio", None)
+ interface_dict = interface.into_dict()
+
+ self.assertEqual(interface.name, "kstdio")
+ self.assertEqual(len(interface.license_hdr), 0)
+ self.assertEqual(interface_dict["name"], "kstdio")
diff --git a/tools/interface_generator/bin/interface_generator.py b/tools/interface_generator/bin/interface_generator.py
new file mode 100755
index 0000000..eab93d7
--- /dev/null
+++ b/tools/interface_generator/bin/interface_generator.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+
+"""
+interface_generator.py
+"""
+
+from argparse import ArgumentParser
+from pathlib import Path
+import sys
+from mako.lookup import TemplateLookup
+from interface_declaration import InterfaceDeclaration
+from templates import get_templates, get_templates_dir
+
+PROG = {
+ "name": "interface_generator",
+ "version": "0.1",
+}
+
+
+def generate_file(
+ template: Path, templates: Path, output, interface: InterfaceDeclaration
+):
+ """generate file using a tempalte and write it to the output location"""
+ lookup = TemplateLookup(directories=[".", templates.absolute()])
+ mako_template = lookup.get_template(str(template.relative_to(templates)))
+ output_name = template.stem.replace("interface", interface.name)
+ print(f"{interface.name} via {template.name} => {output_name}")
+
+ result = mako_template.render(**interface.into_dict(), PROG=PROG)
+
+ if isinstance(output, Path):
+ # print(f"writing to {(output / output_name).absolute()}")
+ with open(output / output_name, "w", encoding="utf-8") as output_file:
+ print(result, file=output_file)
+ else:
+ print(result, file=output)
+
+
+def main():
+ """main function"""
+ parser = ArgumentParser(
+ prog="interface_generator",
+ description="Generate C header and mock files from an interface declaration",
+ )
+ parser.add_argument(
+ "-i",
+ "--interface",
+ type=Path,
+ # required=True,
+ help="path to interface file",
+ )
+ parser.add_argument(
+ "-t",
+ "--templates",
+ type=Path,
+ default=get_templates_dir(),
+ help="templates location",
+ )
+ parser.add_argument(
+ "-l",
+ "--license",
+ type=Path,
+ required=True,
+ help="path to license file",
+ )
+ parser.add_argument(
+ "-o",
+ "--output",
+ type=Path,
+ default=sys.stdout,
+ help="path to output, stdout by default",
+ )
+
+ args = parser.parse_args()
+ # print(args)
+
+ interface = InterfaceDeclaration(args.interface, args.license)
+ # print(interface)
+
+ for template in get_templates(args.templates):
+ # print(template)
+ generate_file(template, args.templates, args.output, interface)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/interface_generator/bin/templates.py b/tools/interface_generator/bin/templates.py
new file mode 100644
index 0000000..9f7539e
--- /dev/null
+++ b/tools/interface_generator/bin/templates.py
@@ -0,0 +1,19 @@
+"""template helper functions"""
+
+from pathlib import Path
+import re
+
+
+TEMPLATE_PATTERN = re.compile(r"^[^_]\S+\.mako$")
+
+
+def get_templates_dir(name="templates") -> Path:
+ """get the templates directory"""
+ return Path(__file__).parent / name
+
+
+def get_templates(path: Path) -> list[Path]:
+ """list templates in given path"""
+ result = list(path.glob("*.mako"))
+ result = [item for item in result if TEMPLATE_PATTERN.match(item.name)]
+ return result
diff --git a/tools/interface_generator/bin/templates/__c_functions.mako b/tools/interface_generator/bin/templates/__c_functions.mako
new file mode 100644
index 0000000..2e40119
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__c_functions.mako
@@ -0,0 +1,4 @@
+/* Functions */
+% for fn in functions:
+${fn['return']} ${name}_${fn['name']}(${ ", ".join(fn['arguments']) });
+% endfor
diff --git a/tools/interface_generator/bin/templates/__c_system_include.mako b/tools/interface_generator/bin/templates/__c_system_include.mako
new file mode 100644
index 0000000..d6a9d09
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__c_system_include.mako
@@ -0,0 +1,4 @@
+/* System includes */
+% for path in system_includes:
+#include <${path}>
+% endfor
diff --git a/tools/interface_generator/bin/templates/__c_types.mako b/tools/interface_generator/bin/templates/__c_types.mako
new file mode 100644
index 0000000..ce6b6b5
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__c_types.mako
@@ -0,0 +1,6 @@
+/* Types */
+% for type in types:
+typedef struct ${name}_${type['name']} {
+${ "\n".join([ " {};".format(member) for member in type['members'] ]) }
+} ${name}_${type['name']};
+% endfor
diff --git a/tools/interface_generator/bin/templates/__header.mako b/tools/interface_generator/bin/templates/__header.mako
new file mode 100644
index 0000000..43ce1e7
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__header.mako
@@ -0,0 +1,5 @@
+/* This file is generated by ${PROG['name']} v${PROG['version']} */
+
+/******************************************************************************
+${ license_hdr }
+ ******************************************************************************/
diff --git a/tools/interface_generator/bin/templates/interface.h.mako b/tools/interface_generator/bin/templates/interface.h.mako
new file mode 100644
index 0000000..47ea940
--- /dev/null
+++ b/tools/interface_generator/bin/templates/interface.h.mako
@@ -0,0 +1,8 @@
+<%include file="__header.mako" />
+#ifndef ${ name.upper() }
+#define ${ name.upper() }
+
+<%include file="__c_system_include.mako" />
+<%include file="__c_types.mako" />
+<%include file="__c_functions.mako" />
+#endif /* ${ name.upper() } */
diff --git a/tools/interface_generator/bin/templates/interface_mock.cpp.mako b/tools/interface_generator/bin/templates/interface_mock.cpp.mako
new file mode 100644
index 0000000..721f2c4
--- /dev/null
+++ b/tools/interface_generator/bin/templates/interface_mock.cpp.mako
@@ -0,0 +1,32 @@
+<%include file="__header.mako" />
+
+#include <stdexcept>
+#include "${name}_mock.hpp"
+
+static I${name}_mock *s_instance = nullptr;
+
+I${name}_mock::I${name}_mock()
+{
+ if(s_instance != nullptr)
+ {
+ throw std::runtime_error("Creating a second instance of mock object");
+ }
+ s_instance = this;
+}
+
+I${name}_mock::~I${name}_mock()
+{
+ // destructors shouldn't throw exceptions
+ s_instance = nullptr;
+}
+
+% for fn in functions:
+${fn['return']} ${name}_${fn['name']}(${ ", ".join(fn['arguments']) })
+{
+ if(s_instance == nullptr)
+ {
+ throw std::runtime_error("No mock created to handle function ${name}_${fn['name']}");
+ }
+ return s_instance->${fn['name']}(${ ", ".join(fn['argument_names']) });
+}
+% endfor
diff --git a/tools/interface_generator/bin/templates/interface_mock.hpp.mako b/tools/interface_generator/bin/templates/interface_mock.hpp.mako
new file mode 100644
index 0000000..e33d50e
--- /dev/null
+++ b/tools/interface_generator/bin/templates/interface_mock.hpp.mako
@@ -0,0 +1,26 @@
+<%include file="__header.mako" />
+#ifndef ${ name.upper() }_MOCK
+#define ${ name.upper() }_MOCK
+
+extern "C" {
+#include "${ name }.h"
+}
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+class I${name}_mock
+{
+public:
+ I${name}_mock();
+ ~I${name}_mock();
+
+ /* Functions */
+% for fn in functions:
+ MOCK_METHOD(${fn['return']}, ${fn['name']}, (${ ", ".join(fn['arguments']) }));
+% endfor
+};
+
+using ${name}_mock = ::testing::NiceMock<I${name}_mock>;
+
+#endif /* ${ name.upper() }_MOCK */
diff --git a/tools/interface_generator/bin/templates_unittest.py b/tools/interface_generator/bin/templates_unittest.py
new file mode 100644
index 0000000..acb1685
--- /dev/null
+++ b/tools/interface_generator/bin/templates_unittest.py
@@ -0,0 +1,30 @@
+"""
+templates unit tests
+"""
+
+import unittest
+from pathlib import Path
+import templates
+
+
+class TemplatesUnittest(unittest.TestCase):
+ """templates unit tests"""
+
+ def test_get_templates_dir_is_valid(self):
+ """verify that default templates dir is valid"""
+ path = templates.get_templates_dir()
+
+ self.assertTrue(isinstance(path, Path))
+ self.assertTrue(path.exists())
+ self.assertTrue(path.is_dir())
+
+ def test_get_templates(self):
+ """verify that templates are available by default"""
+ path = templates.get_templates_dir()
+ result = templates.get_templates(path)
+
+ self.assertGreater(len(result), 0)
+ for template in result:
+ self.assertTrue(isinstance(template, Path))
+ self.assertTrue(template.exists())
+ self.assertTrue(template.is_file())
diff --git a/tools/interface_generator/defs.bzl b/tools/interface_generator/defs.bzl
new file mode 100644
index 0000000..238588f
--- /dev/null
+++ b/tools/interface_generator/defs.bzl
@@ -0,0 +1,53 @@
+def _generate_interface_impl(ctx):
+ out_hdrs = [
+ ctx.actions.declare_file(ctx.attr.interface + ".h"),
+ ctx.actions.declare_file(ctx.attr.interface + "_mock.hpp"),
+ ]
+ out_srcs = [
+ ctx.actions.declare_file(ctx.attr.interface + "_mock.cpp"),
+ ]
+ args = ["-l", ctx.file.license.path, "-o", out_hdrs[0].dirname]
+
+ ctx.actions.run(
+ tools = ctx.attr._interface_generator_tool[DefaultInfo].data_runfiles.files,
+ inputs = [ctx.file.license],
+ outputs = out_srcs + out_hdrs,
+ executable = ctx.executable._interface_generator_tool,
+ arguments = args,
+ mnemonic = "GenerateInterface",
+ )
+
+ compilation_ctx = cc_common.create_compilation_context(
+ headers = depset(out_hdrs),
+ includes = depset([out_hdrs[0].dirname]),
+ )
+
+ return [
+ DefaultInfo(files = depset(out_srcs)),
+ CcInfo(compilation_context = compilation_ctx),
+ ]
+
+generate_interface_rule = rule(
+ implementation = _generate_interface_impl,
+ attrs = {
+ "interface": attr.string(),
+ "license": attr.label(allow_single_file = True),
+ "_interface_generator_tool": attr.label(
+ executable = True,
+ cfg = "exec",
+ allow_files = True,
+ providers = [DefaultInfo],
+ default = Label("//bin:interface_generator"),
+ ),
+ "outs": attr.output_list(),
+ },
+)
+
+def generate_interface(name, interface, license, visibility = None):
+ generate_interface_rule(
+ name = name,
+ interface = interface,
+ outs = [name + "_mock.cpp"],
+ license = license,
+ visibility = visibility,
+ )
diff --git a/tools/interface_generator/private/BUILD.bazel b/tools/interface_generator/private/BUILD.bazel
new file mode 100644
index 0000000..3012bcb
--- /dev/null
+++ b/tools/interface_generator/private/BUILD.bazel
@@ -0,0 +1,9 @@
+""" targets internal to the tool and rule """
+
+package(default_visibility = ["//:__pkg__"])
+
+exports_files([
+ "mypyrc",
+ "pylintrc",
+ "pytest_wrapper.py",
+])
diff --git a/tools/interface_generator/private/defs.bzl b/tools/interface_generator/private/defs.bzl
new file mode 100644
index 0000000..8e2ae8d
--- /dev/null
+++ b/tools/interface_generator/private/defs.bzl
@@ -0,0 +1,29 @@
+load("@pip//:requirements.bzl", "requirement")
+
+def py_pytest(name, srcs, deps = [], data = [], **kwargs):
+ native.py_test(
+ name = name,
+ srcs = ["//private:pytest_wrapper.py"],
+ main = "//private:pytest_wrapper.py",
+ legacy_create_init = False,
+ imports = ["."],
+ args = [
+ "--capture=no",
+ "--black",
+ "--pylint",
+ "--pylint-rcfile=$(location //private:pylintrc)",
+ "--mypy",
+ "--mypy-config-file=$(location //private:mypyrc)",
+ ] + ["$(location :%s)" % x for x in srcs],
+ deps = [
+ requirement("pytest"),
+ requirement("pytest-black"),
+ requirement("pytest-pylint"),
+ requirement("pytest-mypy"),
+ ] + deps,
+ data = [
+ "//private:mypyrc",
+ "//private:pylintrc",
+ ] + srcs + data,
+ **kwargs
+ )
diff --git a/tools/interface_generator/private/mypyrc b/tools/interface_generator/private/mypyrc
new file mode 100644
index 0000000..d787271
--- /dev/null
+++ b/tools/interface_generator/private/mypyrc
@@ -0,0 +1,2 @@
+[mypy]
+disable_error_code = import-untyped
diff --git a/tools/interface_generator/private/pylintrc b/tools/interface_generator/private/pylintrc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/interface_generator/private/pylintrc
diff --git a/tools/interface_generator/private/pytest_wrapper.py b/tools/interface_generator/private/pytest_wrapper.py
new file mode 100755
index 0000000..b4def3b
--- /dev/null
+++ b/tools/interface_generator/private/pytest_wrapper.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+import sys
+import pytest
+
+if __name__ == "__main__":
+ sys.exit(pytest.main(sys.argv[1:]))
diff --git a/tools/interface_generator/requirements.txt b/tools/interface_generator/requirements.txt
new file mode 100644
index 0000000..c285e04
--- /dev/null
+++ b/tools/interface_generator/requirements.txt
@@ -0,0 +1,6 @@
+Mako==1.3.3
+MarkupSafe==2.1.5
+pytest==8.3.2
+pytest-black==0.3.12
+pytest-pylint==0.21.0
+pytest-mypy==0.10.3
diff --git a/tools/interface_generator/requirements_lock.txt b/tools/interface_generator/requirements_lock.txt
new file mode 100644
index 0000000..62c9eb4
--- /dev/null
+++ b/tools/interface_generator/requirements_lock.txt
@@ -0,0 +1,220 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+astroid==3.2.4 \
+ --hash=sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a \
+ --hash=sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25
+ # via pylint
+attrs==24.2.0 \
+ --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \
+ --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2
+ # via pytest-mypy
+black==24.8.0 \
+ --hash=sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6 \
+ --hash=sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e \
+ --hash=sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f \
+ --hash=sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018 \
+ --hash=sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e \
+ --hash=sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd \
+ --hash=sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4 \
+ --hash=sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed \
+ --hash=sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2 \
+ --hash=sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42 \
+ --hash=sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af \
+ --hash=sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb \
+ --hash=sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368 \
+ --hash=sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb \
+ --hash=sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af \
+ --hash=sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed \
+ --hash=sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47 \
+ --hash=sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2 \
+ --hash=sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a \
+ --hash=sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c \
+ --hash=sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920 \
+ --hash=sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1
+ # via pytest-black
+click==8.1.7 \
+ --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
+ --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
+ # via black
+dill==0.3.8 \
+ --hash=sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca \
+ --hash=sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7
+ # via pylint
+filelock==3.15.4 \
+ --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \
+ --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7
+ # via pytest-mypy
+iniconfig==2.0.0 \
+ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
+ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
+ # via pytest
+isort==5.13.2 \
+ --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \
+ --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6
+ # via pylint
+mako==1.3.3 \
+ --hash=sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40 \
+ --hash=sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73
+ # via -r requirements.txt
+markupsafe==2.1.5 \
+ --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \
+ --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \
+ --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \
+ --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \
+ --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \
+ --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \
+ --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \
+ --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \
+ --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \
+ --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \
+ --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \
+ --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \
+ --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \
+ --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \
+ --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \
+ --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \
+ --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \
+ --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \
+ --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \
+ --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \
+ --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \
+ --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \
+ --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \
+ --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \
+ --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \
+ --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \
+ --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \
+ --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \
+ --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \
+ --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \
+ --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \
+ --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \
+ --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \
+ --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \
+ --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \
+ --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \
+ --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \
+ --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \
+ --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \
+ --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \
+ --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \
+ --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \
+ --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \
+ --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \
+ --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \
+ --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \
+ --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \
+ --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \
+ --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \
+ --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \
+ --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \
+ --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \
+ --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \
+ --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \
+ --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \
+ --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \
+ --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \
+ --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \
+ --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \
+ --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68
+ # via
+ # -r requirements.txt
+ # mako
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+mypy==1.11.1 \
+ --hash=sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54 \
+ --hash=sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a \
+ --hash=sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72 \
+ --hash=sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69 \
+ --hash=sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b \
+ --hash=sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe \
+ --hash=sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4 \
+ --hash=sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd \
+ --hash=sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0 \
+ --hash=sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525 \
+ --hash=sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2 \
+ --hash=sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c \
+ --hash=sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5 \
+ --hash=sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de \
+ --hash=sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74 \
+ --hash=sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c \
+ --hash=sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e \
+ --hash=sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58 \
+ --hash=sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b \
+ --hash=sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417 \
+ --hash=sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411 \
+ --hash=sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb \
+ --hash=sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03 \
+ --hash=sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca \
+ --hash=sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8 \
+ --hash=sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08 \
+ --hash=sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809
+ # via pytest-mypy
+mypy-extensions==1.0.0 \
+ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \
+ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782
+ # via
+ # black
+ # mypy
+packaging==24.1 \
+ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
+ --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
+ # via
+ # black
+ # pytest
+pathspec==0.12.1 \
+ --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \
+ --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712
+ # via black
+platformdirs==4.2.2 \
+ --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
+ --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
+ # via
+ # black
+ # pylint
+pluggy==1.5.0 \
+ --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \
+ --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669
+ # via pytest
+pylint==3.2.6 \
+ --hash=sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f \
+ --hash=sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3
+ # via pytest-pylint
+pytest==8.3.2 \
+ --hash=sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5 \
+ --hash=sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce
+ # via
+ # -r requirements.txt
+ # pytest-black
+ # pytest-mypy
+ # pytest-pylint
+pytest-black==0.3.12 \
+ --hash=sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5
+ # via -r requirements.txt
+pytest-mypy==0.10.3 \
+ --hash=sha256:7638d0d3906848fc1810cb2f5cc7fceb4cc5c98524aafcac58f28620e3102053 \
+ --hash=sha256:f8458f642323f13a2ca3e2e61509f7767966b527b4d8adccd5032c3e7b4fd3db
+ # via -r requirements.txt
+pytest-pylint==0.21.0 \
+ --hash=sha256:88764b8e1d5cfa18809248e0ccc2fc05035f08c35f0b0222ddcfea1c3c4e553e \
+ --hash=sha256:f10d9eaa72b9fbe624ee4b55da0481f56482eee0a467afc1ee3ae8b1fefbd0b4
+ # via -r requirements.txt
+toml==0.10.2 \
+ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
+ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
+ # via pytest-black
+tomlkit==0.13.0 \
+ --hash=sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72 \
+ --hash=sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264
+ # via pylint
+typing-extensions==4.12.2 \
+ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
+ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
+ # via mypy
diff --git a/tools/interface_generator/test/BUILD.bazel b/tools/interface_generator/test/BUILD.bazel
new file mode 100644
index 0000000..2166c4e
--- /dev/null
+++ b/tools/interface_generator/test/BUILD.bazel
@@ -0,0 +1,24 @@
+load("//:defs.bzl", "generate_interface")
+
+generate_interface(
+ name = "kstdio",
+ interface = "kstdio",
+ license = "//:LICENSE.md",
+ visibility = ["//visibility:private"],
+)
+
+cc_library(
+ name = "kstdio_mock",
+ srcs = ["kstdio_mock.cpp"],
+ deps = [
+ ":kstdio",
+ "@googletest//:gtest",
+ "@googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "test_kstdio",
+ srcs = ["test_kstdio.cpp"],
+ deps = [":kstdio_mock"],
+)
diff --git a/tools/interface_generator/test/test_kstdio.cpp b/tools/interface_generator/test/test_kstdio.cpp
new file mode 100644
index 0000000..95540a4
--- /dev/null
+++ b/tools/interface_generator/test/test_kstdio.cpp
@@ -0,0 +1,20 @@
+#include "kstdio_mock.hpp"
+
+using ::testing::StrEq;
+
+class kstdioFixture : public ::testing::Test {
+protected:
+ void
+ SetUp()
+ {
+ }
+
+ kstdio_mock m_kstdio_mock;
+};
+
+TEST_F(kstdioFixture, printfCallsMock)
+{
+ EXPECT_CALL(m_kstdio_mock, printf(StrEq("hello world")));
+
+ kstdio_printf("hello world");
+}