pw_system: Add RPC test runner to demo

Registers the unit test handler in the pw_system demo application, and
links in a couple tests to demonstrate running unit tests.

Change-Id: I0ed92f71cfdf17b7edf8cb15e4293eaea3de712d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/95362
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_system/BUILD.gn b/pw_system/BUILD.gn
index fd5cb5a..5d422bc 100644
--- a/pw_system/BUILD.gn
+++ b/pw_system/BUILD.gn
@@ -107,6 +107,7 @@
   public_configs = [ ":public_include_path" ]
   public_deps = [
     ":config",
+    "$dir_pw_rpc:server",
     "$dir_pw_thread:thread_core",
   ]
 }
@@ -217,6 +218,11 @@
     ":pw_system",
     "$dir_pw_log",
     "$dir_pw_thread:sleep",
+    "$dir_pw_unit_test:rpc_service",
+
+    # Adds a test that the test server can run.
+    "$dir_pw_status:status_test.lib",
+    "$dir_pw_string:string_builder_test.lib",
   ]
 }
 
diff --git a/pw_system/example_user_app_init.cc b/pw_system/example_user_app_init.cc
index 93c8415..4289f8b 100644
--- a/pw_system/example_user_app_init.cc
+++ b/pw_system/example_user_app_init.cc
@@ -13,11 +13,19 @@
 // the License.
 
 #include "pw_log/log.h"
+#include "pw_system/rpc_server.h"
 #include "pw_thread/sleep.h"
+#include "pw_unit_test/unit_test_service.h"
+
 namespace pw::system {
 
+pw::unit_test::UnitTestService unit_test_service;
+
 // This will run once after pw::system::Init() completes. This callback must
 // return or it will block the work queue.
-void UserAppInit() { PW_LOG_INFO("Pigweed is fun!"); }
+void UserAppInit() {
+  PW_LOG_INFO("Pigweed is fun!");
+  GetRpcServer().RegisterService(unit_test_service);
+}
 
 }  // namespace pw::system
diff --git a/pw_system/public/pw_system/rpc_server.h b/pw_system/public/pw_system/rpc_server.h
index 3918ded..bf05fcc 100644
--- a/pw_system/public/pw_system/rpc_server.h
+++ b/pw_system/public/pw_system/rpc_server.h
@@ -16,6 +16,7 @@
 
 #include <cstdint>
 
+#include "pw_rpc/server.h"
 #include "pw_system/config.h"
 #include "pw_thread/thread_core.h"
 
diff --git a/pw_system/py/BUILD.gn b/pw_system/py/BUILD.gn
index 53ee336..8e31836 100644
--- a/pw_system/py/BUILD.gn
+++ b/pw_system/py/BUILD.gn
@@ -35,6 +35,8 @@
     "$dir_pw_protobuf_compiler/py",
     "$dir_pw_rpc/py",
     "$dir_pw_tokenizer/py",
+    "$dir_pw_unit_test:unit_test_proto.python",
+    "$dir_pw_unit_test/py",
   ]
   inputs = []
 
diff --git a/pw_system/py/pw_system/console.py b/pw_system/py/pw_system/console.py
index f38b569..56d71e0 100644
--- a/pw_system/py/pw_system/console.py
+++ b/pw_system/py/pw_system/console.py
@@ -62,6 +62,7 @@
 from pw_rpc.console_tools.console import flattened_rpc_completions
 from pw_system.device import Device
 from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
+from pw_unit_test_proto import unit_test_pb2
 
 _LOG = logging.getLogger('tools')
 _DEVICE_LOG = logging.getLogger('rpc_device')
@@ -171,7 +172,6 @@
         help_text=__doc__,
         config_file_path=config_file_path,
     )
-    interactive_console.hide_windows('Host Logs')
     interactive_console.add_sentence_completer(completions)
     if serial_debug:
         interactive_console.add_bottom_toolbar(BandwidthToolbar())
@@ -250,6 +250,7 @@
     # provided, and shadowing errors due to ordering when the default global
     # search path is used.
     compiled_protos.append(log_pb2)
+    compiled_protos.append(unit_test_pb2)
     protos.extend(compiled_protos)
 
     if not protos:
diff --git a/pw_system/py/pw_system/device.py b/pw_system/py/pw_system/device.py
index 1288f68..5a99bfc 100644
--- a/pw_system/py/pw_system/device.py
+++ b/pw_system/py/pw_system/device.py
@@ -21,12 +21,12 @@
 
 from pw_hdlc.rpc import HdlcRpcClient, default_channels
 import pw_log_tokenized
-
 from pw_log.proto import log_pb2
 from pw_rpc import callback_client, console_tools
 from pw_status import Status
 from pw_tokenizer.detokenize import Detokenizer
 from pw_tokenizer.proto import decode_optionally_tokenized
+import pw_unit_test.rpc
 
 # Internal log for troubleshooting this tool (the console).
 _LOG = logging.getLogger('tools')
@@ -79,6 +79,10 @@
         """Returns an object for accessing services on the specified channel."""
         return next(iter(self.client.client.channels())).rpcs
 
+    def run_tests(self, timeout_s: Optional[float] = 5) -> bool:
+        """Runs the unit tests on this device."""
+        return pw_unit_test.rpc.run_tests(self.rpcs, timeout_s=timeout_s)
+
     def listen_to_log_stream(self):
         """Opens a log RPC for the device's unrequested log stream.
 
diff --git a/targets/host_device_simulator/target_docs.rst b/targets/host_device_simulator/target_docs.rst
index 494a4b9..43bf9bd 100644
--- a/targets/host_device_simulator/target_docs.rst
+++ b/targets/host_device_simulator/target_docs.rst
@@ -39,3 +39,19 @@
 
 To communicate with the launched process, use
 ``pw-system-console -s localhost:33000 --proto-globs pw_rpc/echo.proto``.
+
+In the bottom-most pane labeled ``Python Repl``, you'll now be able to send RPC
+commands to the simulated device process. For example, you can send an RPC
+message that will be echoed back:
+
+.. code:: sh
+
+  >>> device.rpcs.pw.rpc.EchoService.Echo(msg='Hello, world!')
+  (Status.OK, pw.rpc.EchoMessage(msg='Hello, world!'))
+
+Or run unit tests included on the simulated device:
+
+.. code:: sh
+
+  >>> device.run_tests()
+  True