Handle termination signals to exit gracefully and prevent resource leak

Make sure we free the reserved resources and kill launched subprocesses
before stopping. Before this patch it was not the case for instance if we
received a SIGTREM signal from kill.

Change-Id: I039e4d1908a04bf606b101ddc6a186ba67e6178e
diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py
index e05f0d7..64de2db 100644
--- a/src/osmo_gsm_tester/suite.py
+++ b/src/osmo_gsm_tester/suite.py
@@ -223,21 +223,26 @@
 
     def run_tests(self, names=None):
         self.log('Suite run start')
-        self.mark_start()
-        if not self.reserved_resources:
-            self.reserve_resources()
-        for test in self.definition.tests:
-            if names and not test.name() in names:
-                test.set_skip()
-                self.test_skipped_ctr += 1
-                self.tests.append(test)
-                continue
-            with self:
-                st = test.run(self)
-                if st == Test.FAIL:
-                    self.test_failed_ctr += 1
-                self.tests.append(test)
-        self.stop_processes()
+        try:
+            self.mark_start()
+            if not self.reserved_resources:
+                self.reserve_resources()
+            for test in self.definition.tests:
+                if names and not test.name() in names:
+                    test.set_skip()
+                    self.test_skipped_ctr += 1
+                    self.tests.append(test)
+                    continue
+                with self:
+                    st = test.run(self)
+                    if st == Test.FAIL:
+                        self.test_failed_ctr += 1
+                    self.tests.append(test)
+        finally:
+            # if sys.exit() called from signal handler (e.g. SIGINT), SystemExit
+            # base exception is raised. Make sure to stop processes in this
+            # finally section. Resources are automatically freed with 'atexit'.
+            self.stop_processes()
         self.duration = time.time() - self.start_timestamp
         if self.test_failed_ctr:
             self.status = SuiteRun.FAIL