CTest support for QEMU/Renode.
* Example usage:
mkdir build
cd build
cmake -GNinja ../
cmake --build . --target AllTests
ctest --verbose # Optional, run with ctest
* Example 2
m test_springbok
Change-Id: I0eb8a162a3271af8e26aea1bacf1069a6566d6aa
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 00c9c8d..42a67bf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,4 +36,16 @@
add_subdirectory(pw_unit_test_demo)
-add_subdirectory(tests)
\ No newline at end of file
+enable_testing()
+
+add_subdirectory(tests)
+
+add_custom_target(AllTests
+ COMMAND
+ "${CMAKE_CTEST_COMMAND}"
+ --verbose
+ --build-and-test "${CMAKE_CURRENT_SOURCE_DIR}"
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ --build-generator "${CMAKE_GENERATOR}"
+ --test-command "${CMAKE_CTEST_COMMAND}"
+)
diff --git a/cmake/vec_cc_test.cmake b/cmake/vec_cc_test.cmake
index 332a085..8857373 100644
--- a/cmake/vec_cc_test.cmake
+++ b/cmake/vec_cc_test.cmake
@@ -3,7 +3,7 @@
_RULE
""
"NAME"
- "SRCS;COPTS;DEFINES;LINKOPTS;DATA;DEPS;LABELS"
+ "SRCS;COPTS;DEFINES;LINKOPTS;DATA;DEPS;LABELS;TIMEOUT"
${ARGN}
)
@@ -26,4 +26,17 @@
${_RULE_LINKOPTS}
)
+if(NOT DEFINED ${_RULE_TIMEOUT})
+ set(_RULE_TIMEOUT 20)
+endif()
+
+find_program(QEMU_RV32 qemu-system-riscv32 HINTS $ENV{OUT}/host/qemu REQUIRED)
+find_program(RENODE_EXE Renode.exe HINTS $ENV{OUT}/host/renode REQUIRED)
+add_test(NAME "qemu_${_RULE_NAME}"
+ COMMAND test_runner.py qemu $<TARGET_FILE:${_RULE_NAME}.elf> --qemu-path ${QEMU_RV32})
+add_test(NAME "renode_${_RULE_NAME}"
+ COMMAND test_runner.py renode $<TARGET_FILE:${_RULE_NAME}.elf> --renode-path ${RENODE_EXE})
+set_tests_properties("renode_${_RULE_NAME}" PROPERTIES TIMEOUT ${_RULE_TIMEOUT})
+set_tests_properties("qemu_${_RULE_NAME}" PROPERTIES TIMEOUT ${_RULE_TIMEOUT})
+
endfunction()
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fe84b42..b46da54 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -2,6 +2,9 @@
enable_language(ASM)
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test_runner.py
+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
add_library(test_v_helpers
test_v_helpers.cpp)
@@ -33,4 +36,5 @@
LINKOPTS
-T${LINKER_SCRIPT}
-Xlinker --defsym=__itcm_length__=256K
+ TIMEOUT 30
)
diff --git a/tests/test_runner.py b/tests/test_runner.py
new file mode 100755
index 0000000..9a3a257
--- /dev/null
+++ b/tests/test_runner.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Runs test within Qemu and Renode simulators."""
+import argparse
+import os
+import sys
+import tempfile
+
+import io
+import pexpect
+
+
+parser = argparse.ArgumentParser(
+ description="Run a springbok test on an simulator.")
+
+parser.add_argument('simulator',
+ help='Select a simulator',
+ choices=['renode', 'qemu'])
+parser.add_argument('elf',
+ help='Elf to execute on a simulator')
+parser.add_argument('--renode-path',
+ help="Path to renode simulator")
+parser.add_argument('--qemu-path',
+ help="Path to qemu simulator")
+
+
+class Simulation: # pylint: disable=too-few-public-methods
+ """ Base class for simulation """
+ def __init__(self, simulator_cmd):
+ self.simulator_cmd = simulator_cmd
+ self.buffer = io.StringIO()
+ self.child = None
+
+ def run(self, timeout=120):
+ """ Run the simulation command and quit the simulation."""
+ self.child = pexpect.spawn(self.simulator_cmd, encoding='utf-8')
+ self.child.logfile = self.buffer
+ self.child.expect("main returned", timeout=timeout)
+ self.child.send("\nq\n")
+ self.child.expect(pexpect.EOF, timeout=timeout)
+ self.child.close()
+ self.buffer.seek(0)
+ return self.buffer.read()
+
+class QemuSimulation(Simulation): # pylint: disable=too-few-public-methods
+ """ Qemu simulation """
+ def __init__(self, path, elf):
+ self.qemu_simulator_cmd = (
+ "%(sim)s -M springbok -nographic -d springbok -device loader,file=%(elf)s")
+ self.sim_params = {"sim": path, "elf": elf}
+ super().__init__(self.qemu_simulator_cmd % self.sim_params)
+
+
+class RenodeSimulation(Simulation): # pylint: disable=too-few-public-methods
+ """ Renode Simulation """
+ def __init__(self, path, elf):
+ # TODO(henryherman): Look at using repl platform file.
+ renode_script = """
+mach create "springbok"
+machine LoadPlatformDescriptionFromString "cpu: CPU.SpringbokRiscV32 @ sysbus"
+machine LoadPlatformDescriptionFromString "ram_vec_imem: Memory.MappedMemory @ sysbus 0x30000000 {size: 0x00040000}"
+machine LoadPlatformDescriptionFromString "ram_vec_dmem: Memory.MappedMemory @ sysbus 0x34000000 {size: 0x00400000}"
+sysbus LoadELF @%s
+sysbus.cpu PC 0x30000000
+sysbus.cpu IsHalted False
+start"""
+ self.renode_script = renode_script % elf
+
+ self.renode_args = [
+ "mono",
+ "%s" % path,
+ '--disable-xwt',
+ ' --console',
+ '--plain',
+ ]
+ self.renode_simulator_cmd = " ".join(self.renode_args)
+ super().__init__(self.renode_simulator_cmd)
+
+ def run(self, timeout=120):
+ file_desc, script_path = tempfile.mkstemp(suffix=".resc")
+ try:
+ with os.fdopen(file_desc, 'w') as tmp:
+ tmp.write(self.renode_script)
+ tmp.flush()
+ self.simulator_cmd += " %s" % script_path
+ test_output = super().run()
+ finally:
+ os.remove(script_path)
+ return test_output
+
+
+Simulators = {
+ "qemu": QemuSimulation,
+ "renode": RenodeSimulation
+}
+
+args = parser.parse_args()
+
+simulators_paths = {
+ "renode": args.renode_path,
+ "qemu": args.qemu_path
+}
+
+
+def main():
+ """ Run a test and check for Pass or Fail """
+ simulator_path = simulators_paths[args.simulator]
+ if simulator_path is None:
+ parser.error(
+ "Must provide path to simulator %s, use argument --%s-path" % (args.simulator,
+ args.simulator))
+
+ simulator_class = Simulators[args.simulator]
+ simulator = simulator_class(simulator_path, args.elf)
+ output = simulator.run()
+ print(output)
+ if "FAILED" in output:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()