pw_presubmit: Add step that tests on hardware

Add "stm32f429i" presubmit step that runs tests on hardware using the
test server. Also add a context manager that launches the test server
and then terminates it.

Change-Id: I026e76cc49f8bbe8a2fe4edb571f27487d33d587
Bug: 377
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/44200
Pigweed-Auto-Submit: Rob Mohr <mohrr@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_presubmit/py/pw_presubmit/build.py b/pw_presubmit/py/pw_presubmit/build.py
index 491809a..38622c1 100644
--- a/pw_presubmit/py/pw_presubmit/build.py
+++ b/pw_presubmit/py/pw_presubmit/build.py
@@ -14,12 +14,14 @@
 """Functions for building code during presubmit checks."""
 
 import collections
+import contextlib
 import itertools
 import json
 import logging
 import os
 from pathlib import Path
 import re
+import subprocess
 from typing import (Collection, Container, Dict, Iterable, List, Mapping, Set,
                     Tuple, Union)
 
@@ -254,3 +256,26 @@
                      '\n'.join(str(x) for x in paths))
 
     return missing
+
+
+@contextlib.contextmanager
+def test_server(executable: str, output_dir: Path):
+    """Context manager that runs a test server executable.
+
+    Args:
+        executable: name of the test server executable
+        output_dir: path to the output directory (for logs)
+    """
+
+    with open(output_dir / 'test_server.log', 'w') as outs:
+        try:
+            proc = subprocess.Popen(
+                [executable, '--verbose'],
+                stdout=outs,
+                stderr=subprocess.STDOUT,
+            )
+
+            yield
+
+        finally:
+            proc.terminate()
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index 9d69b77..0a3f254 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -117,6 +117,13 @@
 
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
+def stm32f429i(ctx: PresubmitContext):
+    build.gn_gen(ctx.root, ctx.output_dir, pw_use_test_server=True)
+    with build.test_server('stm32f429i_disc1_test_server', ctx.output_dir):
+        build.ninja(ctx.output_dir, *_at_all_optimization_levels('stm32f429i'))
+
+
+@filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_nanopb_build(ctx: PresubmitContext):
     build.install_package(ctx.package_root, 'nanopb')
     build.gn_gen(ctx.root,
@@ -641,6 +648,7 @@
     gn_gcc_build,
     renode_check,
     static_analysis,
+    stm32f429i,
 )
 
 LINTFORMAT = (