From 638b14346108542ecb59552ea3b42244d3fd1f8d Mon Sep 17 00:00:00 2001 From: dbedin-microej <87306402+denisbedin@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:53:04 +0100 Subject: [PATCH 1/2] document KF Testsuite creation in SDK 6 (#1135) --- KernelDeveloperGuide/kfTestsuite.rst | 152 +++++++++++++++--- .../kf_testsuite_project_structure_sdk6.png | Bin 0 -> 32039 bytes 2 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 KernelDeveloperGuide/png/kf_testsuite_project_structure_sdk6.png diff --git a/KernelDeveloperGuide/kfTestsuite.rst b/KernelDeveloperGuide/kfTestsuite.rst index 3327cdfe0..4df500c14 100644 --- a/KernelDeveloperGuide/kfTestsuite.rst +++ b/KernelDeveloperGuide/kfTestsuite.rst @@ -13,47 +13,147 @@ Enable the Test Suite In an existing library project: -- Create the ``src/test/projects`` directory, -- Edit the ``module.ivy`` and insert the following line within the ```` XML element: +.. tabs:: - :: + .. tab:: SDK 6 - -- Configure the option ``artifacts.resolver`` to the name of the resolver used to import KF test dependencies. - The name must be one of the resolver names defined in your :ref:`settings file `. - If you are using the default settings file, set the option to ``MicroEJChainResolver``. - This option is usually set as a global :ref:`MMM option `. + - Create the ``src/test/resources/projects`` directory. + - Follow the instructions to :ref:`setup a testsuite on the Simulator `. + - In the build script file, replace the line:: + + microej.useMicroejTestEngine(this) + + by:: + + microej.useMicroejTestEngine(this, TestTarget.EMB, TestMode.PROJECT) + + - Add the ``import`` statements at the beginning of the file:: + + import com.microej.gradle.plugins.TestMode + import com.microej.gradle.plugins.TestTarget + + - Add the required properties as follows: + + .. code-block:: + + val test by getting(JvmTestSuite::class) { + microej.useMicroejTestEngine(this, TestTarget.EMB, TestMode.PROJECT) + + targets { + all { + testTask.configure { + doFirst { + systemProperties = mapOf( + "microej.testsuite.properties.microejtool.deploy.name" to "deployToolBSPRun", + + // Configure the TCP/IP address and port if the VEE Port Run script does not redirect execution traces + "microej.testsuite.properties.testsuite.trace.ip" to "localhost", + "microej.testsuite.properties.testsuite.trace.port" to "5555", + // Tell the testsuite engine that the VEE Port Run script redirects execution traces. + // Uncomment this line and comment the 2 lines above if the VEE Port supports it. + //"microej.testsuite.properties.launch.test.trace.file" to "true" + ) + } + } + } + } + } + + .. tab:: SDK 5 + + - Create the ``src/test/projects`` directory, + - Edit the ``module.ivy`` and insert the following line within the ```` XML element: + + :: + + + - Configure the option ``artifacts.resolver`` to the name of the resolver used to import KF test dependencies. + The name must be one of the resolver names defined in your :ref:`settings file `. + If you are using the default settings file, set the option to ``MicroEJChainResolver``. + This option is usually set as a global :ref:`MMM option `. Add a KF Test ------------- -A KF test is a structured directory placed in the ``src/test/projects`` directory. +A KF test is a structured directory placed in the ``src/test/resources/projects`` directory in SDK 6, or in the ``src/test/projects`` directory in SDK 5. +The creation of a KF is done as follows: + +.. tabs:: + + .. tab:: SDK 6 + + - In the ``src/test/resources/projects`` directory, create a new directory ``newTest`` for the KF test. + - In the ``src/test/java`` folder, create a new Interface with the following content: + + .. code-block:: java + + import org.junit.Test; + + public interface MyTest { -- Create a new directory for the KF test -- Within this directory, create the sub-projects: + @Test + void newTest(); + } + + .. note:: + The name of a KF test is free. For each KF test, a method with the same name and annotated with ``@Test`` must be defined in the Java Interface. + + - Within the ``newTest`` directory, create the sub-projects: - - Create a new directory for the Kernel project and initialize it using the ``microej-javalib`` :ref:`skeleton `, - - Create a new directory for the Feature project and initialize it using the ``application`` :ref:`skeleton `, - - Create a new directory for the Firmware project and initialize it using the ``firmware-multiapp`` :ref:`skeleton `. + - Create a new ``kernel`` directory and :ref:`create a Kernel `. The Kernel must depend on your Library project: -The names of the project directories are free, however MicroEJ suggests the following naming convention, assuming the KF test directory is ``[TestName]``: + .. code-block:: + + implementation("org.example:myLibrary") -- ``[TestName]-kernel`` for the Kernel project, -- ``[TestName]-app[1..N]`` for Feature projects, -- ``[TestName]-firmware`` for the Firmware project. + .. note:: -The KF Test Suite structure shall be similar to the following figure: + The Library project is used as an included build when running one of its KF tests. Therefore, it is not mandatory to specify the dependency version here. -.. figure:: png/kf_testsuite_project_structure.png - :alt: KF Test Suite Structure - :align: center + - Create a new ``app`` directory and :ref:`create an Application `. + - Create a ``settings.gradle.kts`` file and add the following content: - KF Test Suite Overall Structure + .. code-block:: + + include("kernel", "app") -All the projects will be built automatically in the right order based on their dependencies. + Each KF test must contain a Kernel project named ``kernel``. If the KF Test also contains one or more Feature projects, their names must be prefixed by ``app``. -KF Test Suite Options ---------------------- + The KF Test Suite structure shall be similar to the following figure: + + .. figure:: png/kf_testsuite_project_structure_sdk6.png + :alt: KF Test Suite Structure + :align: center + + KF Test Suite Overall Structure + + .. tab:: SDK 5 + + - Create a new directory for the KF test + - Within this directory, create the sub-projects: + + - Create a new directory for the Kernel project and initialize it using the ``microej-javalib`` :ref:`skeleton `, + - Create a new directory for the Feature project and initialize it using the ``application`` :ref:`skeleton `, + - Create a new directory for the Firmware project and initialize it using the ``firmware-multiapp`` :ref:`skeleton `. + + The names of the project directories are free, however MicroEJ suggests the following naming convention, assuming the KF test directory is ``[TestName]``: + + - ``[TestName]-kernel`` for the Kernel project, + - ``[TestName]-app[1..N]`` for Feature projects, + - ``[TestName]-firmware`` for the Firmware project. + + The KF Test Suite structure shall be similar to the following figure: + + .. figure:: png/kf_testsuite_project_structure.png + :alt: KF Test Suite Structure + :align: center + + KF Test Suite Overall Structure + + All the projects will be built automatically in the right order based on their dependencies. + +KF Test Suite Options (SDK 5 only) +---------------------------------- It is possible to configure the same options defined by :ref:`Test Suite Options ` for the KF test suite, by using the prefix ``microej.kf.testsuite.properties`` instead of ``microej.testsuite.properties``. diff --git a/KernelDeveloperGuide/png/kf_testsuite_project_structure_sdk6.png b/KernelDeveloperGuide/png/kf_testsuite_project_structure_sdk6.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1a2f8cb8fcc7b70b74db7c813216a98969bf7a GIT binary patch literal 32039 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 Vp>aS8(A@mD$4bu>i{xMW|1YWIeo6oU literal 0 HcmV?d00001 From 4a49ff3b265f2e8fda4b7ee28300ca09a1bc7e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Delhom=C3=A9nie?= Date: Fri, 17 Jan 2025 17:08:13 +0100 Subject: [PATCH 2/2] Document stackTraceReader task --- SDK6UserGuide/stackTraceReader.rst | 224 +++++++++++------------------ SDKUserGuide/stackTraceReader.rst | 6 +- conf.py | 1 + requirements.txt | 1 + 4 files changed, 86 insertions(+), 146 deletions(-) diff --git a/SDK6UserGuide/stackTraceReader.rst b/SDK6UserGuide/stackTraceReader.rst index ee2290b45..7e1eb115f 100644 --- a/SDK6UserGuide/stackTraceReader.rst +++ b/SDK6UserGuide/stackTraceReader.rst @@ -27,66 +27,66 @@ libraries, the BSP, the OS, and the compiled Application). It prints the decoded stack trace. When :ref:`Multi-Sandbox capability ` is enabled, -the stack trace reader can simultaneously decode heterogeneous stack +the Stack Trace Reader can simultaneously decode heterogeneous stack traces with lines owned by different Sandboxed Applications and the Kernel. Lines owned by the Kernel can be decoded with the Executable debug information file (optionally made available by your Kernel provider). -Use (Standalone Application) -============================ +Usage +===== -For example, write the following new line to dump the currently executed stack trace on the -standard output. +To decode an Application stack trace, the Stack Trace Reader tool requires the Application Executable file. -.. code:: java +In an Application project +^^^^^^^^^^^^^^^^^^^^^^^^^ - package com.mycompany; - - public class Test { - public static void main(String[] args) { - System.out.println("Hello, World!"); - new Exception().printStackTrace(); - } - } +If you have an Application project and want to decode a stack trace for this Application, you can execute the tool in the project. -To decode an application stack trace, the stack trace reader tool requires the application executable ELF file. -In the case of a platform with full BSP connection (see :ref:`BSP Connection Cases `), the file is ``application.out`` in the output folder. -In the other cases, the ELF file is generated by the C toolchain when building the BSP project (usually a ``.out`` or ``.axf`` file). +.. tip:: -On successful deployment, the application is started on the device and -the following trace is dumped on standard output. + .. collapse:: Click here if you don't have an Application to test yet and want to quickly test the Stack Trace Reader tool. -.. code:: console + If you don't have an Application to test yet, you can create an :ref:`Application project ` with the following example main class: - VM START - Hello World from Gradle! - Exception in thread "main" @C:0x8070c30@ - at @C:0x8070c60@.@M:0x8075850:0x807585a@ - at @C:0x8070c30@.@M:0x80769a4:0x80769ba@ - at @C:0x8070c30@.@M:0x807857c:0x8078596@ - at @C:0x8070c00@.@M:0x8074e04:0x8074e1a@ - at @C:0x8070ce0@.@M:0x807601c:0x807603c@ - at @C:0x806fe10@.@M:0x807779c:0x80777b0@ - at @C:0x8070c00@.@M:0x8077b40:0x8077b4c@ - at @C:0x8070c00@.@M:0x80779b0:0x80779bb@ + .. code:: java -To decode the trace, execute the ``execTool`` task: + package com.mycompany; + + public class MyApp { + public static void main(String[] args) { + System.out.println("Hello, World!"); + new Exception().printStackTrace(); + } + } -.. warning:: + On successful deployment (by executing the ``runOnDevice`` task), + the Application is started on the device and the trace is dumped on the standard output. - This tool requires to use Gradle **8.8** maximum. + .. code:: console -.. code:: console + VM START + Hello, World! + Exception in thread "main" @C:0x8070c30@ + at @C:0x8070c60@.@M:0x8075850:0x807585a@ + at @C:0x8070c30@.@M:0x80769a4:0x80769ba@ + at @C:0x8070c30@.@M:0x807857c:0x8078596@ + at @C:0x8070c00@.@M:0x8074e04:0x8074e1a@ + at @C:0x8070ce0@.@M:0x807601c:0x807603c@ + at @C:0x806fe10@.@M:0x807779c:0x80777b0@ + at @C:0x8070c00@.@M:0x8077b40:0x8077b4c@ + at @C:0x8070c00@.@M:0x80779b0:0x80779bb@ - ./gradlew execTool --name=stackTraceDecrypter \ - --toolProperty=proxy.connection.connection.type="console" \ - --toolProperty=application.file="../../application/executable/application.out" \ - --toolProperty=additional.application.files="" \ - --console plain +Follow these steps: -Paste the previous trace dump into the console. -The output of the Stack Trace Reader is the following: +- Make sure to build the Executable before executing the tool, by running either the ``buildExecutable`` or the ``runOnDevice`` task. + The Executable file produced by the Application project is automatically used if it exists. +- When the Executable of the Application project is built, execute the ``stackTraceReader`` task, either from your IDE, or as a command line:: + + ./gradlew stackTraceReader + +- When the message ``[INFO] Paste the MicroEJ core engine stack trace here.`` is displayed, paste the encoded stacktrace into the console. + The Stack Trace Reader immediately displays the decoded stack trace: .. code:: console @@ -94,7 +94,7 @@ The output of the Stack Trace Reader is the following: console: [INFO] Paste the MicroEJ core engine stack trace here. VM START - Hello World from Gradle! + Hello, World! Exception in thread "main" @C:0x8070c30@ at @C:0x8070c60@.@M:0x8075850:0x807585a@ at @C:0x8070c30@.@M:0x80769a4:0x80769ba@ @@ -105,7 +105,7 @@ The output of the Stack Trace Reader is the following: at @C:0x8070c00@.@M:0x8077b40:0x8077b4c@ at @C:0x8070c00@.@M:0x80779b0:0x80779bb@ VM START - Hello World from Gradle! + Hello, World! Exception in thread "main" java.lang.Throwable at java.lang.System.getStackTrace(Unknown Source) at java.lang.Throwable.fillInStackTrace(Throwable.java:82) @@ -116,114 +116,52 @@ The output of the Stack Trace Reader is the following: at java.lang.Thread.runWrapper(Thread.java:464) at java.lang.Thread.callWrapper(Thread.java:449) +The Stack Trace Reader tool interacts with the console by default. +See the :ref:`sdk6.section.stacktrace.reader.tool.configure` chapter to learn about the other modes and configurations available. -Options -======= - -Option: Executable file -^^^^^^^^^^^^^^^^^^^^^^^ - -*Option Name*: ``application.file`` - -*Required?*: Yes - -*Description*: - -Specify the full path of a full linked elf file. - -Option: Additional object files +Custom Executable File Location ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -*Option Name*: ``additional.application.files`` - -*Required?*: Yes - -Option: Connection type -^^^^^^^^^^^^^^^^^^^^^^^ - -*Option Name*: ``proxy.connection.connection.type`` - -*Required?*: Yes - -*Available values*: - -- ``console`` -- ``file`` -- ``uart`` -- ``socket`` - -*Description*: - -Specify the connection type between the device and PC. - -Option: Port -^^^^^^^^^^^^ - -*Option Name*: ``pcboardconnection.usart.pc.port`` - -*Required?*: (For ``uart`` Connection Type) - -*Description*: +If you want to decode a stack trace of a different Executable file than the Application project one, +you must set the ``application.file`` System Property to define the Executable file location:: + ./gradlew stackTraceReader -D"application.file"="/path/to/my/application.out" -*Format:* ``port name`` +.. _sdk6.section.stacktrace.reader.tool.configure: +Configure +^^^^^^^^^ -Specifies the PC COM port: +The Stack Trace Reader tool uses by default the console to communicate with the device, +but this can be changed by setting the ``stackTraceReaderConnectionMode`` property in the ``microej`` block in the ``build.gradle.kts`` file:: -- Windows - ``COM1``, ``COM2``, ``...``, ``COM*n*`` -- Linux - ``/dev/ttyS0``, ``/dev/ttyS1``, ``...``, ``/dev/ttyS*n*`` - -Option: Baudrate -^^^^^^^^^^^^^^^^ - -*Option Name*: ``pcboardconnection.usart.pc.baudrate`` - -*Required?*: (For ``uart`` Connection Type) - -*Available values*: - -- ``9600`` -- ``38400`` -- ``57600`` -- ``115200`` - -*Description*: - -Defines the COM baudrate for PC-Device communication. - -Option: Port -^^^^^^^^^^^^ - -*Option Name*: ``pcboardconnection.socket.port`` - -*Required?*: (For ``socket`` Connection Type) - -*Description*: - -IP port. - -Option: Address -^^^^^^^^^^^^^^^ - -*Option Name*: ``pcboardconnection.socket.address`` - -*Required?*: (For ``socket`` Connection Type) - -*Description*: - -IP address, on the form A.B.C.D. or empty. - -Option: Stack trace file -^^^^^^^^^^^^^^^^^^^^^^^^ - -*Option Name*: ``pcboardconnection.file.path`` - -*Required?*: - -*Description*: + microej { + stackTraceReaderConnectionMode = "file" + stackTraceReaderFilePath = "/path/to/input/file" + stackTraceReaderFileResultPath = "/path/to/output/file" + } -Path to a stack trace file or empty. +Each mode has dedicated additional options. +The list of the available modes, with their dedicated options, are: + +- ``console`` (default mode): use the standard input/output. +- ``file``: use files. + - ``stackTraceReaderFilePath``: Path to the file containing the encoded stack trace. + - ``stackTraceReaderFileResultPath``: Path to the output file for the decoded stack trace. +- ``uart``: use Serial communication. + - ``stackTraceReaderUartPort``: PC COM port (example for Windows: ``COM1``, example for Linux: ``/dev/ttyS1``). + - ``stackTraceReaderUartBaudRate``: COM baudrate for PC-Device communication. +- ``socket``: use a socket. + - ``stackTraceReaderSocketAddress``: IP address. + - ``stackTraceReaderSocketPort``: IP port. + +For example, here is the configuration to use the ``socket`` mode on the address ``192.168.1.17`` and the port ``4000``:: + + microej { + stackTraceReaderConnectionMode = "socket" + stackTraceReaderUartPort = "192.168.1.17" + stackTraceReaderUartBaudRate = "4000" + } .. | Copyright 2008-2025, MicroEJ Corp. Content in this space is free diff --git a/SDKUserGuide/stackTraceReader.rst b/SDKUserGuide/stackTraceReader.rst index a6c5a19b8..518cecb78 100644 --- a/SDKUserGuide/stackTraceReader.rst +++ b/SDKUserGuide/stackTraceReader.rst @@ -29,7 +29,7 @@ libraries, the BSP, the OS, and the compiled MicroEJ Application). It prints the decoded stack trace. When :ref:`Multi-Sandbox capability ` is enabled, -the stack trace reader can simultaneously decode heterogeneous stack +the Stack Trace Reader can simultaneously decode heterogeneous stack traces with lines owned by different MicroEJ Sandboxed Applications and the firmware. Lines owned by the firmware can be decoded with the firmware debug information file (optionally made available by your firmware provider). @@ -60,7 +60,7 @@ standard output. Code to Dump a Stack Trace -To decode an application stack trace, the stack trace reader tool requires the application executable ELF file. +To decode an application stack trace, the Stack Trace Reader tool requires the application executable ELF file. In the case of a platform with full BSP connection (see :ref:`BSP Connection Cases `), the file is ``application.out`` in the output folder. In the other cases, the ELF file is generated by the C toolchain when building the BSP project (usually a ``.out`` or ``.axf`` file). @@ -129,7 +129,7 @@ standard output. Code to Dump a Stack Trace -To decode an application stack trace, the stack trace reader +To decode an application stack trace, the Stack Trace Reader tool requires the application binary file with debug information (``application.fodbg`` in the output folder). Note that the file uploaded on the device is ``application.fo`` (stripped version diff --git a/conf.py b/conf.py index cf4d8e26e..99c00cc05 100644 --- a/conf.py +++ b/conf.py @@ -27,6 +27,7 @@ 'sphinx.ext.graphviz', 'sphinx_copybutton', 'sphinx_tabs.tabs', + 'sphinx_toolbox.collapse', ] sphinx_tabs_valid_builders = ['linkcheck'] diff --git a/requirements.txt b/requirements.txt index 643142ef7..12b094e21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ sphinx_rtd_theme==2.1.0rc1 sphinx-copybutton==0.3.1 docutils==0.18.1 sphinx-tabs==3.4.1 +sphinx-toolbox==3.6.0 \ No newline at end of file