make prematurely exited processes fail the test

Change-Id: I54394c40718b44378df597e32003742059052869
diff --git a/src/osmo_gsm_tester/log.py b/src/osmo_gsm_tester/log.py
index 2ad82aa..a4f78df 100644
--- a/src/osmo_gsm_tester/log.py
+++ b/src/osmo_gsm_tester/log.py
@@ -48,6 +48,9 @@
 # may be overridden by regression tests
 get_process_id = lambda: '%d-%d' % (os.getpid(), time.time())
 
+class Error(Exception):
+    pass
+
 class LogTarget:
     do_log_time = None
     do_log_category = None
@@ -166,13 +169,7 @@
         else:
             loglevel = ''
 
-        log_line = [str(m) for m in messages]
-
-        if named_items:
-            # unfortunately needs to be sorted to get deterministic results
-            log_line.append('{%s}' %
-                            (', '.join(['%s=%r' % (k,v)
-                             for k,v in sorted(named_items.items())])))
+        log_line = [compose_message(messages, named_items)]
 
         if deeper_origins:
             log_line.append(' [%s]' % deeper_origins)
@@ -316,6 +313,10 @@
         Origin._global_current_origin, self._parent_origin = self._parent_origin, None
         return rc
 
+    def raise_exn(self, *messages, exn_class=Error, **named_items):
+        with self:
+            raise exn_class(compose_message(messages, named_items))
+
     def redirect_stdout(self):
         return contextlib.redirect_stdout(SafeRedirectStdout(self))
 
@@ -498,4 +499,15 @@
         log_exn()
         return return_on_failure
 
+def compose_message(messages, named_items):
+    msgs = [str(m) for m in messages]
+
+    if named_items:
+        # unfortunately needs to be sorted to get deterministic results
+        msgs.append('{%s}' %
+                    (', '.join(['%s=%r' % (k,v)
+                     for k,v in sorted(named_items.items())])))
+
+    return ' '.join(msgs)
+
 # vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/osmo_gsm_tester/process.py b/src/osmo_gsm_tester/process.py
index 4cf1b8d..8152ff0 100644
--- a/src/osmo_gsm_tester/process.py
+++ b/src/osmo_gsm_tester/process.py
@@ -133,8 +133,20 @@
             self.log('Terminated', rc=self.result)
         else:
             self.err('Terminated: ERROR', rc=self.result)
-            #self.err('stdout:\n', self.get_stdout_tail(prefix='| '), '\n')
-            self.err('stderr:\n', self.get_stderr_tail(prefix='| '), '\n')
+            #self.log_stdout_tail()
+            self.log_stderr_tail()
+
+    def log_stdout_tail(self):
+        m = self.get_stdout_tail(prefix='| ')
+        if not m:
+            return
+        self.log('stdout:\n', m, '\n')
+
+    def log_stderr_tail(self):
+        m = self.get_stderr_tail(prefix='| ')
+        if not m:
+            return
+        self.log('stderr:\n', m, '\n')
 
     def close_output_logs(self):
         self.dbg('Cleanup')
@@ -154,6 +166,9 @@
         if self.result is not None:
             self.cleanup()
 
+    def is_running(self):
+        return self.process_obj is not None and self.result is None
+
     def get_output(self, which):
         v = self.outputs.get(which)
         if not v:
diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py
index 0b8927f..2d6c67b 100644
--- a/src/osmo_gsm_tester/suite.py
+++ b/src/osmo_gsm_tester/suite.py
@@ -250,10 +250,14 @@
         self.wait(lambda: False, timeout=seconds)
 
     def poll(self):
+        ofono_client.poll()
         if self._processes:
             for process in self._processes:
                 process.poll()
-        ofono_client.poll()
+                if not process.is_running():
+                    process.log_stdout_tail()
+                    process.log_stderr_tail()
+                    process.raise_exn('Process ended prematurely')
 
     def prompt(self, *msgs, **msg_details):
         'ask for user interaction. Do not use in tests that should run automatically!'