Repository: google/openhtf

Issue:
RuntimeError is raised when looping Execute
I put Execute in a loop:

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

Raises RuntimeError

python hello_world.py
Provide a DUT ID in order to start the test.
test
Hello World!
Finishing test execution with outcome PASS.
Traceback (most recent call last):
  File "hello_world.py", line 88, in <module>
    test.Execute(test_start=triggers.PromptForTestStart())
  File "/home/source/venv/local/lib/python2.7/site-packages/openhtf-0.9-py2.7.egg/openhtf/**init**.py", line 176, in Execute
    self._executor.Start()
  File "/home/source/venv/local/lib/python2.7/site-packages/openhtf-0.9-py2.7.egg/openhtf/exe/__init__.py", line 89, in Start
    self.start()
  File "/usr/lib/python2.7/threading.py", line 739, in start
    raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once



Discussion:
Dang, `exe.TestExecutor` is a thread that can't be started more than once.

I have an old wheel I built 4/11 that allows me to loop.   Any way we can revert back to prior method?


Resolving patch:
diff --git a/openhtf/__init__.py b/openhtf/__init__.py
--- a/openhtf/__init__.py
+++ b/openhtf/__init__.py
@@ -82,8 +82,8 @@ 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._executor = exe.TestExecutor(self._test_data, plugs.PlugManager())
     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
     # to force them to if they want to use defaults.  For default values, see
@@ -139,8 +139,11 @@ def Stop(self, *_):
       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: %s', self._executor.GetState())
-      self._executor.Stop()
+      _LOG.error('Stopping Test due to SIGINT')
+      if self._executor:
+        _LOG.error('Test state: %s', self._executor.GetState())
+        self._executor.Stop()
+        self._executor = None
 
   def Execute(self, test_start=None, loop=None):
     """Starts the framework and executes the given test.
@@ -166,6 +169,7 @@ def Execute(self, test_start=None, loop=None):
                   self.data.code_info.name)
         return
 
+      self._executor = exe.TestExecutor(self._test_data, plugs.PlugManager())
       _LOG.info('Executing test: %s', self.data.code_info.name)
       self._executor.SetTestStart(test_start)
       http_server = None
@@ -173,6 +177,7 @@ def Execute(self, test_start=None, loop=None):
         http_server = http_api.Server(
           self._executor, self._test_options.http_port)
         http_server.Start()
+
       self._executor.Start()
 
     try:
@@ -185,6 +190,7 @@ def Execute(self, test_start=None, loop=None):
         self.OutputTestRecord(record)
       if http_server:
         http_server.Stop()
+      self._executor = None
 
 
 class TestOptions(mutablerecords.Record('TestOptions', [], {


Test patch: