diff --git a/test/NnxBuildFlow.py b/test/NnxBuildFlow.py new file mode 100644 index 0000000..877ea2b --- /dev/null +++ b/test/NnxBuildFlow.py @@ -0,0 +1,98 @@ +import os +import subprocess +from abc import ABC, abstractmethod +from enum import Enum +from pathlib import Path +from typing import Dict, Type + +from NnxMapping import NnxName + + +class NnxBuildFlow(ABC): + + @abstractmethod + def __init__(self, nnxName: NnxName) -> None: ... + + @abstractmethod + def build(self) -> None: ... + + @abstractmethod + def run(self) -> str: ... + + @abstractmethod + def __str__(self) -> str: ... + + @staticmethod + def cmd_run(cmd: str, env=None) -> str: + proc = subprocess.run( + cmd.split(), check=True, capture_output=True, text=True, env=env + ) + return proc.stdout + + +class MakeBuildFlow(NnxBuildFlow): + BUILD_CMD = "make -C app all platform=gvsoc" + RUN_CMD = "make -C app run platform=gvsoc" + + def __init__(self, nnxName: NnxName) -> None: + self.nnxName = nnxName + + def env(self) -> os._Environ: + _env = os.environ + _env["ACCELERATOR"] = str(self.nnxName) + return _env + + def build(self) -> None: + Path("app/src/nnx_layer.c").touch() + _ = NnxBuildFlow.cmd_run(MakeBuildFlow.BUILD_CMD, self.env()) + + def run(self) -> str: + return NnxBuildFlow.cmd_run(MakeBuildFlow.RUN_CMD, self.env()) + + def __str__(self) -> str: + return "make" + + +class CmakeBuildFlow(NnxBuildFlow): + BINARY_NAME = "test-pulp-nnx" + TOOLCHAIN_FILE = "cmake/toolchain_llvm.cmake" + GVSOC_TARGET = "siracusa" + + def __init__(self, nnxName: NnxName) -> None: + self.nnxName = nnxName + self.build_dir = os.path.abspath(f"app/build_{nnxName}") + self.gvsoc_workdir = os.path.join(self.build_dir, "gvsoc_workdir") + assert "GVSOC" in os.environ, "The GVSOC environment variable is not set." + + def prepare(self) -> None: + os.makedirs(self.gvsoc_workdir, exist_ok=True) + subprocess.run( + f"cmake -Sapp -B{self.build_dir} -GNinja -DCMAKE_TOOLCHAIN_FILE={CmakeBuildFlow.TOOLCHAIN_FILE} -DACCELERATOR={self.nnxName}".split(), + check=True, + ) + + def build(self) -> None: + _ = NnxBuildFlow.cmd_run(f"cmake --build {self.build_dir}") + + def run(self) -> str: + bin = os.path.join(self.build_dir, CmakeBuildFlow.BINARY_NAME) + gvsoc = os.environ["GVSOC"] + cmd = f"{gvsoc} --binary {bin} --work-dir {self.gvsoc_workdir} --target {CmakeBuildFlow.GVSOC_TARGET} image flash run" + return NnxBuildFlow.cmd_run(cmd) + + def __str__(self) -> str: + return "cmake" + + +class NnxBuildFlowName(Enum): + make = "make" + cmake = "cmake" + + def __str__(self) -> str: + return self.value + + +NnxBuildFlowClsMapping: Dict[NnxBuildFlowName, Type[NnxBuildFlow]] = { + NnxBuildFlowName.make: MakeBuildFlow, + NnxBuildFlowName.cmake: CmakeBuildFlow, +} diff --git a/test/conftest.py b/test/conftest.py index f44d288..70776ea 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -23,6 +23,7 @@ import pydantic import pytest +from NnxBuildFlow import CmakeBuildFlow, NnxBuildFlowName from NnxMapping import NnxMapping, NnxName from NnxTestClasses import NnxTest, NnxTestGenerator from TestClasses import implies @@ -68,9 +69,10 @@ def pytest_addoption(parser): ) parser.addoption( "--build-flow", - dest="build_flow", - choices=["make", "cmake"], - default="make", + dest="buildFlowName", + type=NnxBuildFlowName, + choices=list(NnxBuildFlowName), + default=NnxBuildFlowName.make, help="Choose the build flow. Default: make", ) @@ -85,10 +87,10 @@ def pytest_generate_tests(metafunc): regenerate = metafunc.config.getoption("regenerate") timeout = metafunc.config.getoption("timeout") nnxName = metafunc.config.getoption("accelerator") - build_flow = metafunc.config.getoption("build_flow") + buildFlowName = metafunc.config.getoption("buildFlowName") assert implies( - build_flow == "cmake", nnxName == "neureka_v2" + buildFlowName == NnxBuildFlowName.cmake, nnxName == NnxName.neureka_v2 ), "The cmake build flow has been tested only with the neureka_v2 accelerator" if recursive: @@ -122,17 +124,10 @@ def pytest_generate_tests(metafunc): ) ) - if build_flow == "cmake": - build_dir = os.path.abspath(f"app/build_{nnxName}") - gvsoc_workdir = os.path.join(build_dir, "gvsoc_workdir") - os.makedirs(gvsoc_workdir, exist_ok=True) - assert "GVSOC" in os.environ, "The GVSOC environment variable is not set." - subprocess.run( - f"cmake -Sapp -B{build_dir} -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain_llvm.cmake -DACCELERATOR={nnxName}".split(), - check=True, - ) + if buildFlowName == NnxBuildFlowName.cmake: + CmakeBuildFlow(nnxName).prepare() metafunc.parametrize("nnxName", [nnxName]) - metafunc.parametrize("build_flow", [build_flow]) + metafunc.parametrize("buildFlowName", [buildFlowName]) metafunc.parametrize("nnxTestName", nnxTestNames) metafunc.parametrize("timeout", [timeout]) diff --git a/test/test.py b/test/test.py index c67d237..5adcc72 100644 --- a/test/test.py +++ b/test/test.py @@ -23,6 +23,7 @@ from pathlib import Path from typing import Dict, Literal, Optional, Tuple, Type, Union +from NnxBuildFlow import NnxBuildFlowClsMapping, NnxBuildFlowName from NnxMapping import NnxMapping, NnxName from NnxTestClasses import NnxTest, NnxTestConf, NnxTestHeaderGenerator, NnxWeight @@ -107,42 +108,9 @@ def assert_message(msg: str, test_name: str, stdout: str, stderr: Optional[str] return retval -def build(nnxName: NnxName, flow: Literal["make", "cmake"]) -> None: - env = os.environ - - if flow == "make": - Path("app/src/nnx_layer.c").touch() - cmd = "make -C app all platform=gvsoc" - env["ACCELERATOR"] = str(nnxName) - elif flow == "cmake": - cmd = "cmake --build app/build" - - subprocess.run(cmd.split(), check=True, capture_output=True, text=True, env=env) - - -def run(nnxName: NnxName, flow: Literal["make", "cmake"]) -> str: - env = os.environ - - if flow == "make": - cmd = "make -C app run platform=gvsoc" - env["ACCELERATOR"] = str(nnxName) - elif flow == "cmake": - build_dir = os.path.abspath(f"app/build_{nnxName}") - bin = os.path.join(build_dir, "test-pulp-nnx") - gvsoc = env["GVSOC"] - gvsoc_workdir = os.path.join(build_dir, "gvsoc_workdir") - cmd = f"{gvsoc} --binary {bin} --work-dir {gvsoc_workdir} --target siracusa image flash run" - - proc = subprocess.run( - cmd.split(), check=True, capture_output=True, text=True, env=env - ) - - return proc.stdout - - def test( nnxName: NnxName, - build_flow: Literal["cmake", "make"], + buildFlowName: NnxBuildFlowName, nnxTestName: str, timeout: int, ): @@ -153,8 +121,9 @@ def test( NnxTestHeaderGenerator(weightCls).generate(nnxTestName, nnxTest) - build(nnxName, build_flow) - stdout = run(nnxName, build_flow) + buildFlow = NnxBuildFlowClsMapping[buildFlowName](nnxName) + buildFlow.build() + stdout = buildFlow.run() match_success = re.search(r"> Success! No errors found.", stdout) match_fail = re.search(r"> Failure! Found (\d*)/(\d*) errors.", stdout)