def test_sequence(self): """Prove the sequence of events on a transaction-decorated method. We expect it to look like: lock get the wrapper if necessary invoke the method while the method raises etag error, refresh the wrapper and re-invoke unlock """ txfx = self.useFixture(fx.WrapperTaskFx(self.dwrap)) @tx.entry_transaction def blacklist_this(wrapper_or_getter): # Always converted by now self.assertIsInstance(wrapper_or_getter, ewrap.EntryWrapper) return self.retry_twice(wrapper_or_getter, self.tracker, txfx) # With an EntryWrapperGetter, get() is invoked self.assertEqual(self.dwrap, blacklist_this(self.getter)) self.assertEqual([ 'lock', 'get', 'update 1', 'refresh', 'update 2', 'refresh', 'update 3', 'unlock' ], txfx.get_log()) # With an EntryWrapper, get() is not invoked self.tracker.counter = 0 txfx.reset_log() self.assertEqual(self.dwrap, blacklist_this(self.dwrap)) self.assertEqual([ 'lock', 'update 1', 'refresh', 'update 2', 'refresh', 'update 3', 'unlock' ], txfx.get_log())
def test_flag_update(self): """flag_update=False avoids update even if Subtask returns True.""" txfx = self.useFixture(fx.WrapperTaskFx(self.dwrap)) tx1 = tx.WrapperTask('tx1', self.getter) tx1.add_functor_subtask(lambda x: True, flag_update=False) tx1.execute() self.assertEqual(0, txfx.patchers['update'].mock.call_count) # But if there's another Subtask that returns True without # flag_update=False, it does trigger an update. tx1.add_functor_subtask(lambda x: True) tx1.execute() self.assertEqual(1, txfx.patchers['update'].mock.call_count)
def test_logspec(self): txfx = self.useFixture(fx.WrapperTaskFx(self.dwrap)) tx1 = tx.WrapperTask('tx1', self.getter) mock_log = mock.Mock() mock_log.side_effect = lambda *args: txfx.log('log') def functor(wrp): txfx.log('functor') # "False" logspec ignored tx1.add_functor_subtask(functor, logspec=[]) # logspec must have at least two args self.assertRaises(ValueError, tx1.add_functor_subtask, functor, logspec=[1]) # First arg must be callable self.assertRaises(ValueError, tx1.add_functor_subtask, functor, logspec=[1, 2]) # Valid call with just a string tx1.add_functor_subtask(functor, logspec=[mock_log, "string"]) # Valid call with a format string and args tx1.add_functor_subtask(functor, logspec=[mock_log, "one %s two %s", 1, 2]) # Valid call with named args tx1.add_functor_subtask(functor, logspec=[ mock_log, "three %(three)s four %(four)s", { 'three': 3, 'four': 4 } ]) tx1.execute() self.assertEqual([ 'lock', 'get', 'functor', 'log', 'functor', 'log', 'functor', 'log', 'functor', 'unlock' ], txfx.get_log()) mock_log.assert_has_calls([ mock.call("string"), mock.call("one %s two %s", 1, 2), mock.call("three %(three)s four %(four)s", { 'three': 3, 'four': 4 }) ])
def test_wrapper_task2(self): # Now: # o Fake like update forces retry # o Test add_functor_subtask, including chaining # o Ensure GET is deferred when .wrapper() is not called ahead of time. # o Make sure subtask args are getting to the subtask. txfx = fx.WrapperTaskFx(self.dwrap) def _update_retries_twice(timeout=-1): self.assertEqual(123, timeout) return self.retry_twice(self.dwrap, self.tracker, txfx) txfx.patchers['update'].side_effect = _update_retries_twice self.useFixture(txfx) def functor(wrapper, arg1, arg2, kwarg3=None, kwarg4=None): txfx.log('functor') # Make sure args are getting here self.assertEqual(['arg', 1], arg1) self.assertEqual('arg2', arg2) self.assertIsNone(kwarg3) self.assertEqual('kwarg4', kwarg4) return wrapper, True # Instantiate-add-execute chain tx.WrapperTask( 'tx2', self.getter, update_timeout=123).add_functor_subtask(functor, ['arg', 1], 'arg2', kwarg4='kwarg4').execute() # Check the overall order. Update should have been called thrice (two # retries) self.assertEqual(3, txfx.patchers['update'].mock.call_count) self.assertEqual([ 'lock', 'get', 'functor', 'update 1', 'refresh', 'functor', 'update 2', 'refresh', 'functor', 'update 3', 'unlock' ], txfx.get_log())
def test_subtask_provides(self): self.useFixture(fx.WrapperTaskFx(self.dwrap)) test_case = self class ChainSubtask(tx.Subtask): def __init__(self, val, *args, **kwargs): self.val = val super(ChainSubtask, self).__init__(*args, **kwargs) def execute(self, *args, **kwargs): test_case.assertEqual(test_case.dwrap, args[0]) # If execute accepts **kwargs, 'provided' is provided. test_case.assertIn('provided', kwargs) test_case.assertEqual(kwargs['expected_provided'], kwargs['provided']) return self.val class ChainSubtask2(tx.Subtask): def execute(self, wrp, provided, expected_provided): test_case.assertEqual(test_case.dwrap, wrp) # Able to get 'provided' as a named parameter test_case.assertEqual(expected_provided, provided) wtsk = tx.WrapperTask('name', self.getter) wtsk.add_subtask(ChainSubtask(1, provides='one', expected_provided={})) # Can't add another Subtask with the same 'provides' self.assertRaises(ValueError, wtsk.add_subtask, ChainSubtask(2, provides='one')) # Next subtask should see the result from the first. wtsk.add_subtask( ChainSubtask(2, provides='two', expected_provided={'one': 1})) # Add one that doesn't provide. Its return shouldn't show up in # 'provided'. wtsk.add_subtask( ChainSubtask(3, expected_provided={ 'one': 1, 'two': 2 })) # 'provided' works implicitly when it's a named parameter on execute wtsk.add_subtask(ChainSubtask2(expected_provided={'one': 1, 'two': 2})) # Even when execute doesn't return anything, we 'provide' that None wtsk.add_subtask( ChainSubtask2(provides='four', expected_provided={ 'one': 1, 'two': 2 })) # Make sure the same stuff works for functors def ret_val_kwargs(*args, **kwargs): self.assertEqual(self.dwrap, args[0]) self.assertIn('provided', kwargs) self.assertEqual(kwargs['expected_provided'], kwargs['provided']) return args[1] def ret_val_explicit(wrp, val, provided, expected_provided): self.assertEqual(self.dwrap, wrp) self.assertEqual(expected_provided, provided) return val self.assertRaises(ValueError, wtsk.add_functor_subtask, int, provides='one') wtsk.add_functor_subtask(ret_val_kwargs, 5, provides='five', expected_provided={ 'one': 1, 'two': 2, 'four': None }) wtsk.add_functor_subtask(ret_val_kwargs, 6, expected_provided={ 'one': 1, 'two': 2, 'four': None, 'five': 5 }) wtsk.add_functor_subtask(ret_val_explicit, 7, provides='seven', expected_provided={ 'one': 1, 'two': 2, 'four': None, 'five': 5 }) wtsk.add_functor_subtask(ret_val_explicit, 8, expected_provided={ 'one': 1, 'two': 2, 'four': None, 'five': 5, 'seven': 7 }) # Execute the WrapperTask, verifying assertions in ChainSubtask[2] and # ret_val_{kwargs|explicit) wrapper, subtask_rets = wtsk.execute() self.assertEqual(self.dwrap, wrapper) # Verify final form of subtask_rets returned from WrapperTask.execute() self.assertEqual( { 'one': 1, 'two': 2, 'four': None, 'five': 5, 'seven': 7 }, subtask_rets)
def test_wrapper_task1(self): txfx = self.useFixture(fx.WrapperTaskFx(self.dwrap)) # Must supply a wrapper or getter to instantiate self.assertRaises(ValueError, tx.WrapperTask, 'foo', 'bar') # Create a valid WrapperTask tx1 = tx.WrapperTask('tx1', self.getter) self.assertEqual('tx1', tx1.name) self.assertIn('wrapper_getter_uuid', tx1.provides) self.assertIn('subtask_rets_getter_uuid', tx1.provides) # Nothing has been run yet self.assertEqual([], txfx.get_log()) # Try running with no subtasks self.assertRaises(ex.WrapperTaskNoSubtasks, tx1.execute) # Try adding something that isn't a Subtask self.assertRaises(ValueError, tx1.add_subtask, 'Not a Subtask') # Error paths don't run anything. self.assertEqual([], txfx.get_log()) # Add a subtask that doesn't change anything tx1.add_subtask( self.LparNameAndMem('z3-9-5-126-127-00000001', logger=txfx)) # Adding a subtask does not run anything self.assertEqual([], txfx.get_log()) # Get the wrapper - this should invoke GET, but *not* under lock self.assertEqual(self.dwrap, tx1.wrapper) self.assertEqual(['get'], txfx.get_log()) # Run the transaction lwrap, subtask_rets = tx1.execute() # The name should be unchanged self.assertEqual('z3-9-5-126-127-00000001', lwrap.name) # And update should not have been called, which should be reflected in # the log. Note that 'get' is NOT called a second time. self.assertEqual([ 'get', 'lock', 'LparNameAndMem_z3-9-5-126-127-00000001', 'unlock' ], txfx.get_log()) self.assertEqual({}, subtask_rets) txfx.reset_log() # These subtasks do change the name. tx1.add_subtask(self.LparNameAndMem('new_name', logger=txfx)) tx1.add_subtask(self.LparNameAndMem('newer_name', logger=txfx)) # But this one doesn't. We're making sure the last 'no update needed' # doesn't make the overall update_needed status False. tx1.add_subtask(self.LparNameAndMem('newer_name', logger=txfx)) # Get the wrapper - this should *not* reinvoke GET self.assertEqual(self.dwrap, tx1.wrapper) self.assertEqual([], txfx.get_log()) # Now execute the transaction lwrap, subtask_rets = tx1.execute() # The last change should be the one that stuck self.assertEqual('newer_name', lwrap.name) # Check the overall order. Update was called. self.assertEqual([ 'lock', 'LparNameAndMem_z3-9-5-126-127-00000001', 'LparNameAndMem_new_name', 'LparNameAndMem_newer_name', 'LparNameAndMem_newer_name', 'update', 'unlock' ], txfx.get_log()) self.assertEqual({}, subtask_rets) # Test 'cloning' the subtask list txfx.reset_log() tx2 = tx.WrapperTask('tx2', self.getter, subtasks=tx1.subtasks) # Add another one to make sure it goes at the end tx2.add_subtask(self.LparNameAndMem('newest_name', logger=txfx)) # Add one to the original transaction to make sure it doesn't affect # this one. tx1.add_subtask(self.LparNameAndMem('bogus_name', logger=txfx)) lwrap, subtask_rets = tx2.execute() # The last change should be the one that stuck self.assertEqual('newest_name', lwrap.name) # Check the overall order. This one GETs under lock. Update called. self.assertEqual([ 'lock', 'get', 'LparNameAndMem_z3-9-5-126-127-00000001', 'LparNameAndMem_new_name', 'LparNameAndMem_newer_name', 'LparNameAndMem_newer_name', 'LparNameAndMem_newest_name', 'update', 'unlock' ], txfx.get_log()) self.assertEqual({}, subtask_rets)