Repository: google/openhtf

Issue:
SigInt isn't stopping tester configured for looping
Add loop to all_the_things:

```
 while True:
    test.Execute(test_start=triggers.PromptForTestStart())
```

python all_the_things.py 
Provide a DUT ID in order to start the test.
test
Hello World!
What's the widget type?
^C2016-06-01 19:50:24,605 - ERROR - Received SIGINT.
2016-06-01 19:50:24,605 - ERROR - Stopping Test due to SIGINT
2016-06-01 19:50:24,606 - ERROR - Test state: <TestState: test@wallaceb-glaptop Running Phase: hello_world>
Tearing down <ExamplePlug: 0>
^C2016-06-01 19:50:42,645 - ERROR - Received SIGINT.
^C



Discussion:
I added sys.exit() at the end of Stop() in openhtf/**init**.py and I see that SIGINT stops the tester and plugs tear down methods are called.

python all_the_things.py 
Provide a DUT ID in order to start the test.
test
Hello World!
What's the widget type?
^C2016-06-01 21:35:43,877 - ERROR - Received SIGINT.
2016-06-01 21:35:43,877 - ERROR - Stopping Test due to SIGINT
2016-06-01 21:35:43,878 - ERROR - Test state: <TestState: Running Phase: hello_world>
Tearing down <ExamplePlug: 0>


Resolving patch:
diff --git a/openhtf/__init__.py b/openhtf/__init__.py
--- a/openhtf/__init__.py
+++ b/openhtf/__init__.py
@@ -82,7 +82,6 @@ def __init__(self, *phases, **metadata):
     self._test_options = TestOptions()
     self._test_data = TestData(phases, metadata=metadata, code_info=code_info)
     self._lock = threading.Lock()
-    self._stopped = False
     self._executor = None
     # Make sure Configure() gets called at least once before Execute().  The
     # user might call Configure() again to override options, but we don't want
@@ -90,7 +89,7 @@ def __init__(self, *phases, **metadata):
     # the class definition of TestOptions.
     self.Configure()
     # TODO(madsci): Fix this to play nice with multiple Test instances.
-    signal.signal(signal.SIGINT, self.Stop)
+    signal.signal(signal.SIGINT, self.StopFromSigInt)
 
   def AddOutputCallback(self, callback):
     """DEPRECATED: Use AddOutputCallbacks() instead."""
@@ -133,19 +132,21 @@ def Configure(self, **kwargs):
     for key, value in kwargs.iteritems():
       setattr(self._test_options, key, value)
 
-  def Stop(self, *_):
+  def StopFromSigInt(self, *_):
     """Stop test execution as abruptly as we can, only in response to SIGINT."""
     _LOG.error('Received SIGINT.')
     with self._lock:
-      # Once we're stopped for SIGINT, don't try to start anything else.
-      self._stopped = True
-      # TestState str()'s nicely to a descriptive string, so let's log that
-      # just for good measure.
       _LOG.error('Stopping Test due to SIGINT')
       if self._executor:
+        # TestState str()'s nicely to a descriptive string, so let's log that
+        # just for good measure.
         _LOG.error('Test state: %s', self._executor.GetState())
         self._executor.Stop()
         self._executor = None
+    # The default SIGINT handler does this. If we don't, then nobody above
+    # us is notified of the event. This will raise this exception in the main
+    # thread.
+    raise KeyboardInterrupt()
 
   def Execute(self, test_start=None, loop=None):
     """Starts the framework and executes the given test.
@@ -161,15 +162,10 @@ def Execute(self, test_start=None, loop=None):
           'DEPRECATED. Looping is no longer natively supported by OpenHTF, '
           'use a while True: loop around Test.Execute() instead.')
 
-    # We have to lock this section to ensure we don't call TestExecutor.Stop()
-    # in self.Stop() between instantiating it and .Start()'ing it.  We'll check
-    # self._stopped to make sure self.Stop() hasn't already been called here.
+    # We have to lock this section to ensure we don't call
+    # TestExecutor.StopFromSigInt() in self.Stop() between instantiating it and
+    # .Start()'ing it.
     with self._lock:
-      if self._stopped:
-        _LOG.info('Cowardly refusing to execute test after SIGINT: %s',
-                  self.data.code_info.name)
-        return
-
       self._executor = exe.TestExecutor(self._test_data, plugs.PlugManager(),
                                         self._test_options.teardown_function)
       _LOG.info('Executing test: %s', self.data.code_info.name)


Test patch: