def testSystemExitOnUnhandledExceptionInEventLoopThread(
    self, _modelRunnerProxyClassMock):
    # Test that an unhandled exception in SlotAgent's event loop thread results
    # in os._exit().

    modelFinishedQ = Queue.Queue()
    def modelFinishedCallback(modelID, exitStatus):
      modelFinishedQ.put((modelID, exitStatus))

    # Patch os._exit and run model in SlotAgent
    osExitCodeQ = Queue.Queue()
    with patch.object(os, "_exit", autospec=True,
                      side_effect=osExitCodeQ.put):
      sa = slot_agent.SlotAgent(slotID=1)

      modelID = "abc"
      sa.startModel(
        modelID=modelID,
        modelFinishedCallback=partial(modelFinishedCallback, modelID))

      # Wait for the call to os._exit()
      # NOTE: if we get the Queue.Empty exception, it means that we didn't get
      #  the expected call to os._exit()
      exitCode = osExitCodeQ.get(timeout=5)
      self.assertEqual(
        exitCode,
        slot_agent._EXIT_CODE_ON_UNHANDLED_EXCEPTION_IN_THREAD)
      self.assertNotEqual(exitCode, 0)
  def testCreateSlotAgentAndCloseIt(self, _modelRunnerProxyClassMock):
    sa = slot_agent.SlotAgent(slotID=1)
    self.assertIsNotNone(sa._thread)

    t = threading.Thread(target=sa.close)
    t.setDaemon(True)
    t.start()
    t.join(timeout=5)
    self.assertFalse(t.isAlive())
    self.assertIsNone(sa._thread)
  def testSwapModelsInSlotAgent(self, modelRunnerProxyClassMock):
    # Create mock ModelRunnerProxy factory
    modelRunnerProxyMocks = []
    def createModelRunnerProxyMock(
      modelID, onTermination, logger):  # pylint: disable=W0613
      modelRunnerProxyMock = Mock(
        spec_set=slot_agent.ModelRunnerProxy,
        stopGracefully=Mock(
          spec_set=slot_agent.ModelRunnerProxy.stopGracefully,
          return_value=0))

      modelRunnerProxyMocks.append(modelRunnerProxyMock)

      return modelRunnerProxyMock


    modelRunnerProxyClassMock.side_effect = createModelRunnerProxyMock

    modelFinishedQ = Queue.Queue()

    def modelFinishedCallback(modelID, exitStatus):
      modelFinishedQ.put((modelID, exitStatus))

    sa = slot_agent.SlotAgent(slotID=1)

    # Test swapping of the models in the single SlotAgent instance
    modelIDs = ["abc", "def"]

    for modelID in modelIDs:
      sa.startModel(
        modelID=modelID,
        modelFinishedCallback=partial(modelFinishedCallback, modelID))
      sa.stopModel()
      self.assertEqual((modelID, 0), modelFinishedQ.get(timeout=5))
      sa.releaseSlot()

    # Close slot agent
    t = threading.Thread(target=sa.close)
    t.setDaemon(True)
    t.start()
    t.join(timeout=5)
    self.assertFalse(t.isAlive())
    self.assertIsNone(sa._thread)

    # Verify ModelRunnerProxy constructor calls
    self.assertEqual(modelRunnerProxyClassMock.call_count, len(modelIDs))

    # Very modelRunnerProxyMock.stopGracefully calls
    self.assertEqual(len(modelRunnerProxyMocks), len(modelIDs))
    for modelRunnerProxyMock in modelRunnerProxyMocks:
      self.assertEqual(modelRunnerProxyMock.stopGracefully.call_count, 1)