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