def test_thread_wrapper_can_be_exited_if_started(self):
        mock = Mock()
        tw = ThreadWrapper(lambda x: None)
        tw.on_stopped(mock)
        tw.start()
        tw.exit()

        self.assertEqual(1, mock.call_count)
    def test_callback_is_called_when_thread_stops(self):
        mock = Mock(return_value=None)

        tw = ThreadWrapper(lambda _: None)
        tw.on_stopped(mock)

        try:
            for i in range(3):
                tw.start()
                tw.stop().wait()
                self.assertEqual(ThreadWrapper.STOPPED, tw.state)
                self.assertNotEqual(0, mock.call_count)
        finally:
            tw.exit()
    def test_exiting_thread_can_not_be_restarted(self):
        counter = 0

        def thread_func(ctx):
            nonlocal counter
            counter += 1
            while not ctx.stop_requested:
                ctx.sleep(0.1)

        def _try_restart():
            self.assertRaises(AssertionError, tw.start)

        tw = ThreadWrapper(thread_func)
        tw.on_stopped(_try_restart)
        tw.start()

        tw.exit()
        self.assertEqual(1, counter)
    def test_stop_callbacks_called_when_thread_fn_exits(self):
        evt = Event()

        def test_fn(_):
            pass

        tw = ThreadWrapper(test_fn)

        try:
            for i in range(1, 3):
                with self.subTest(f'Run #{i}'):
                    tw.on_stopped(evt.set)
                    evt.clear()
                    tw.start()
                    if not evt.wait(2):
                        self.fail('Thread function has not exited properly')

        finally:
            tw.exit()
    def test_exception_stops_properly(self):
        evt = Event()

        def _dummy_thread_fn():  # wrong signature, results in TypeError
            pass

        tw = ThreadWrapper(_dummy_thread_fn)

        try:
            for i in range(1, 3):
                with self.subTest(f'Run #{i}'):
                    tw.on_stopped(evt.set)  # set callback first to verify it will be called after clear
                    evt.clear()
                    if not tw.start().wait(2):
                        self.fail('Thread was not started properly')

                    if not evt.wait(2):
                        self.fail('Thread was not stopped properly')
        finally:
            tw.exit()