def test_cannot_recursively_use_context_manager(self): with thread_meeting.participate() as one: self.assertEqual("(primary)", one.name) with self.assertRaises(RuntimeError) as context: with thread_meeting.participate(): pass self.assertTrue("' already present" in str(context.exception), str(context.exception))
def test_transcribe_sees_attendee_enter_exit(self): with thread_meeting.transcriber() as transcriber: with thread_meeting.participate("Bilbo"): pass with thread_meeting.participate("Baggins"): pass expected = (TI('Transcript', TT.Enter), TI('Thread ID:', TT.Enter), TI('Thread ID:', TT.Exit), TI('Thread ID:', TT.Enter), TI('Thread ID:', TT.Exit), TI('Transcript', TT.Exit)) self.verify_transcript_items(transcriber, *expected)
def create_enter_exit_instance(self): """ Since EnterExit objects can't be directly created, this helper function returns something that is an EnterExit object. """ return thread_meeting.participate("Me")
def test_transcribe_sees_baton_enter_exit(self): with thread_meeting.transcriber() as transcriber: with thread_meeting.participate("Bilbo") as bilbo: with bilbo.request_baton(): pass expected = (TI('Transcript', TT.Enter), TI('Thread ID:', TT.Enter), TI('Baton', TT.Enter), TI('Baton', TT.Exit), TI('Thread ID:', TT.Exit), TI('Transcript', TT.Exit)) self.verify_transcript_items(transcriber, *expected)
def hold_baton(data: dict): with thread_meeting.participate("hold_baton") as me: with me.request_baton() as baton: data['baton'] = baton data['state'] = 'baton' while data['state'] == 'baton': time.sleep(0.1) data['state'] = 'done' while data['state'] == 'done': time.sleep(0.1)
def thread_entry(self): """ Thread entry point launched by start_all for each worker. """ with thread_meeting.participate( self._requested_name) as self._attendee: self._fad = FuncAndData(self.on_idle) while self.state != WorkerState.FINAL: try: # If we have any delayed messages to add to the queue, # then add them (to the back). self._check_for_delayed_messages() # We're going to expand data as kwargs, so it must be # in dict format... even if there's nothing in it. data = self._fad.data if self._fad.data else dict() # Don't log enter/exit for on_* functions in the base class # if they are not overloaded, but do for everything else. if self._fad.func in self._no_transcript_for: func = self._fad.func else: func = transcribe_func(self, self._fad.func) # Run the function and find out what the next function is. if self._fad.data: func_return = func(**data) else: func_return = func() if callable(func_return): # If the callee just returned the next function, # there's no data. Wrap it into a FuncAndData for them. self._fad = FuncAndData(func_return) elif isinstance(func_return, FuncAndData): # The callee returned exactly what we want. self._fad = func_return elif func_return is None: # No instructions. state = self.state if state == WorkerState.FINAL: self._fad = None elif bool(self._queue()): # queue has items in it, we want to process next. self._fad = FuncAndData(self.on_message) else: self._fad = FuncAndData(self.on_idle) else: raise RuntimeError( "Illegal return value from function") except BaseException as e: self._debug("Exception detected, thread FAILED") raise return self.state
def test_attendee_can_use_queue(self): with thread_meeting.participate("Bilbo"): # could have also use ... `as me` in the with statement. queue = thread_meeting.me().queue self.assertFalse(queue) thread_meeting.me().note("one", 1) self.assertTrue(queue) head = queue.get() self.assertFalse(queue) self.assertIsInstance(head, thread_meeting.Take) self.assertEqual(head.payload, 1) self.assertEqual(head.name, "one")
def test_transcribe_sees_baton_enter_exit_with_exception(self): with thread_meeting.transcriber() as transcriber: with self.assertRaises(RuntimeError) as context: with thread_meeting.participate("Bilbo") as bilbo: with bilbo.request_baton(): raise RuntimeError('this error is expected') self.assertTrue("this error is expected" in str(context.exception), str(context.exception)) expected = (TI('Transcript', TT.Enter), TI('Thread ID:', TT.Enter), TI('Baton', TT.Enter), TI('Baton', TT.Exit), TI('Thread ID:', TT.Exit), TI('Transcript', TT.Exit)) self.verify_transcript_items(transcriber, *expected)
def test_transcribe_sees_attendee_add_to_queue(self): with thread_meeting.transcriber() as transcriber: # ENTER Transcript with thread_meeting.participate("Bilbo") as me: # ENTER Bilbo me.note("Ring", "The One Ring") # NOTE Ring me.note("Sword", "Sting") # NOTE Sword me.queue.get().protest() # NACK Ring # exit with scope here # EXIT Bilbo me.queue.get() # ACK sword # exit with scope here # EXIT Transcript expected = (TI('Transcript', TT.Enter), TI('Thread ID:', TT.Enter), TI('Ring', TT.Post), TI('Sword', TT.Post), TI('Ring', TT.Nack), TI('Thread ID:', TT.Exit), TI('Sword', TT.Ack), TI('Transcript', TT.Exit)) self.verify_transcript_items(transcriber, *expected)
def test_can_find_me_in_scope(self): with thread_meeting.participate("Bilbo") as bilbo: # Find bilbo through a function call. me = thread_meeting.me() self.assertEqual(me.name, bilbo.name) self.assertTrue(bilbo) self.assertTrue(me) # Ensure they are equivalent: get the baton with me. with me.request_baton() as baton: self.assertIsNotNone(baton) # Ensure that bilbo and me both went invalid when # we left the 'with' statement. self.assertFalse(bilbo) self.assertFalse(me) # And ensure that if I try to find me outside the with, # only None is returned. me = thread_meeting.me() self.assertIsNone(me)
def test_attendee_cannot_get_baton_held_in_other_thread(self): data = dict(state='before') worker = threading.Thread(target=self.hold_baton, args=(data, )) worker.start() while data['state'] == 'before': time.sleep(0.1) with thread_meeting.participate("Bilbo") as me: # The worker should have the baton, we should not. with me.request_baton() as baton: self.assertIsNone(baton) self.assertTrue(data['baton']) data['state'] = 'after' while data['state'] == 'after': time.sleep(0.1) # Worker should have given up the baton, we can wait. self.assertFalse(data['baton']) with me.request_baton() as baton: self.assertIsNotNone(baton) # Tell the worker to quit, and wait on it. data['state'] = 'quit' worker.join()
def test_attendee_name_will_default(self): with thread_meeting.participate() as attendee: self.assertEqual("(primary)", attendee.name)
def test_attendee_can_get_baton_twice(self): with thread_meeting.participate("Bilbo") as me: with me.request_baton() as baton: self.assertIsNotNone(baton) with me.request_baton() as baton: self.assertIsNotNone(baton)
def test_attendee_cannot_recursively_get_baton(self): with thread_meeting.participate("Bilbo") as me: with me.request_baton() as baton: self.assertIsNotNone(baton) with me.request_baton() as baton2: self.assertIsNone(baton2)
def test_attendee_cannot_change_name_in_meeting(self): with thread_meeting.participate("Bilbo") as one: with self.assertRaises(AttributeError) as context: one.name = "Baggins" self.assertTrue("can't set attribute" in str(context.exception), str(context.exception))
def test_attendee_can_change_names_while_gone(self): with thread_meeting.participate("Bilbo") as one: self.assertEqual(one.name, "Bilbo") with thread_meeting.participate("Baggins") as one: self.assertEqual(one.name, "Baggins")
def test_attendee_can_leave_and_come_back(self): with thread_meeting.participate() as one: self.assertEqual("(primary)", one.name) with thread_meeting.participate() as two: self.assertEqual("(primary)", two.name)
def test_attendee_knows_name(self): with thread_meeting.participate("Bilbo") as attendee: self.assertEqual("Bilbo", attendee.name)
def test_can_create_attendee_indirectly(self): with thread_meeting.participate("Bilbo") as attendee: self.assertIsInstance(attendee, thread_meeting.Attendee)