[v2,2/4] create a minimal python unittest infrastructure

Message ID 20230509074412.86392-3-tobias.schaffner@siemens.com
State Superseded, archived
Headers show
Series Rewrite the image-account-extension in python | expand

Commit Message

Tobias Schaffner May 9, 2023, 7:44 a.m. UTC
From: Tobias Schaffner <tobias.schaffner@siemens.com>

Add some some infrastructure for python unittesting. The unittest_isar
module exposes a function that uses the bb.parse module to import
python functions that are defined in bitbake files.

Signed-off-by: Tobias Schaffner <tobias.schaffner@siemens.com>
---
 testsuite/unittests/README.md  | 28 +++++++++++++++++++++
 testsuite/unittests/bitbake.py | 37 ++++++++++++++++++++++++++++
 testsuite/unittests/rootfs.py  | 45 ++++++++++++++++++++++++++++++++++
 3 files changed, 110 insertions(+)
 create mode 100644 testsuite/unittests/README.md
 create mode 100644 testsuite/unittests/bitbake.py
 create mode 100644 testsuite/unittests/rootfs.py

Patch

diff --git a/testsuite/unittests/README.md b/testsuite/unittests/README.md
new file mode 100644
index 00000000..75a3bb01
--- /dev/null
+++ b/testsuite/unittests/README.md
@@ -0,0 +1,28 @@ 
+# Isar Unittests
+
+The unittest python module adds some simple infrastructure that allows to
+unittest python functions defined in bitbake files.
+
+## Running the tests
+
+You can run the tests using avocado with `avocado --show=app,test run testsuite/unittests/`
+or by using the buildin module with `python3 -m unittest discover testsuite/unittests/`
+
+## Creating new tests
+
+See the [unittest documentation](https://docs.python.org/3/library/unittest.html)
+on how to create a test module and name it test_*bitbake_module_name*.py
+
+Use the function `load_function(file_name: str, function_name: str) -> Callable`
+in the bitbake module to load the function.
+
+Example:
+```python
+from bitbake import load_function
+
+my_function = load_function("meta/classes/my_module.bbclass", "my_function")
+my_function(arg1, arg2)
+```
+
+Use the [unittest.mock](https://docs.python.org/3/library/unittest.mock.html)
+library to mock the bb modules as needed.
diff --git a/testsuite/unittests/bitbake.py b/testsuite/unittests/bitbake.py
new file mode 100644
index 00000000..1e2f685a
--- /dev/null
+++ b/testsuite/unittests/bitbake.py
@@ -0,0 +1,37 @@ 
+# This software is a part of ISAR.
+# Copyright (C) Siemens AG, 2023
+#
+# SPDX-License-Identifier: MIT
+
+import sys
+import pathlib
+from typing import Callable
+
+location = pathlib.Path(__file__).parent.resolve()
+sys.path.insert(0, "{}/../../bitbake/lib".format(location))
+
+from bb.parse import handle
+from bb.data import init
+
+# Modules added for reimport from testfiles
+from bb.data_smart import DataSmart
+
+
+def load_function(file_name: str, function_name: str) -> Callable:
+    """Load a python function defined in a bitbake file.
+
+    Args:
+        file_name (str): The path to the file e.g. `meta/classes/my_special.bbclass`.
+        function_name (str): The name of the python function without braces e.g. `my_special_function`
+
+    Returns:
+        Callable: The loaded function.
+    """
+    d = init()
+    parse = handle("{}/../../{}".format(location, file_name), d)
+    if function_name not in parse:
+        raise KeyError("Function {} does not exist in {}".format(
+            function_name, file_name))
+    namespace = {}
+    exec(parse[function_name], namespace)
+    return namespace[function_name]
diff --git a/testsuite/unittests/rootfs.py b/testsuite/unittests/rootfs.py
new file mode 100644
index 00000000..6c511493
--- /dev/null
+++ b/testsuite/unittests/rootfs.py
@@ -0,0 +1,45 @@ 
+# This software is a part of ISAR.
+# Copyright (C) Siemens AG, 2023
+#
+# SPDX-License-Identifier: MIT
+
+import tempfile
+import pathlib
+import shutil
+import atexit
+
+temp_dirs = []
+
+
+class TemporaryRootfs:
+    """ A temporary rootfs folder that will be removed after the testrun. """
+
+    def __init__(self):
+        self._rootfs_path = tempfile.mkdtemp()
+        temp_dirs.append(self._rootfs_path)
+
+    def path(self) -> str:
+        return self._rootfs_path
+
+    def create_file(self, path: str, content: str) -> None:
+        """ Create a file with the given content.
+
+        Args:
+            path (str): The path to the file e.g. `/etc/hostname`.
+            content (str): The content of the file e.g. `my_special_host`
+
+        Returns:
+            None
+        """
+        pathlib.Path(self._rootfs_path +
+                     path).parent.mkdir(parents=True, exist_ok=True)
+        with open(self._rootfs_path + path, 'w') as file:
+            file.write(content)
+
+
+def cleanup():
+    for temp_dir in temp_dirs:
+        shutil.rmtree(temp_dir)
+
+
+atexit.register(cleanup)