def testCallsFunctionEveryTimeWhenMinTimeBetweenCallsZero(self): decorated = cache.WithLimitedCallFrequency(rdfvalue.Duration(0))( self.mock_fn) for _ in range(10): decorated() self.assertEqual(self.mock_fn.call_count, 10)
def testDecoratedFunctionsAreWaitedForPerArguments(self): event = threading.Event() def Fn(x): if x != 42: event.wait() return x mock_fn = mock.Mock(wraps=Fn) compatibility.SetName(mock_fn, "foo") # Expected by functools.wraps. decorated = cache.WithLimitedCallFrequency( rdfvalue.Duration.From(30, rdfvalue.SECONDS))( mock_fn) def T(): decorated(1) t = threading.Thread(target=T) t.start() try: # This should return immediately. There's another function call # in progress, but with different arguments, so no locking should occur. # I.e. decorated(1) and decorated(42) shouldn't influence each other. decorated(42) finally: event.set() t.join() self.assertEqual(mock_fn.call_count, 2)
def testPropagatesExceptions(self): mock_fn = mock.Mock(side_effect=ValueError()) compatibility.SetName(mock_fn, "foo") # Expected by functools.wraps. decorated = cache.WithLimitedCallFrequency( rdfvalue.Duration.From(30, rdfvalue.SECONDS))(mock_fn) with self.assertRaises(ValueError): decorated()
def testExceptionIsNotCached(self): mock_fn = mock.Mock(side_effect=ValueError()) compatibility.SetName(mock_fn, "foo") # Expected by functools.wraps. decorated = cache.WithLimitedCallFrequency( rdfvalue.Duration.From(30, rdfvalue.SECONDS))(mock_fn) for _ in range(10): with self.assertRaises(ValueError): decorated() self.assertEqual(mock_fn.call_count, 10)
def testCallsFunctionOnceInGivenTimeRangeWhenMinTimeBetweenCallsNonZero( self): decorated = cache.WithLimitedCallFrequency( rdfvalue.DurationSeconds("30s"))(self.mock_fn) now = rdfvalue.RDFDatetime.Now() with test_lib.FakeTime(now): r1 = decorated() with test_lib.FakeTime(now + rdfvalue.DurationSeconds("15s")): r2 = decorated() self.assertEqual(r1, r2) self.assertEqual(self.mock_fn.call_count, 1) with test_lib.FakeTime(now + rdfvalue.DurationSeconds("30s")): r3 = decorated() self.assertNotEqual(r1, r3) self.assertEqual(self.mock_fn.call_count, 2)
def testDecoratedFunctionIsNotExecutedConcurrently(self): event = threading.Event() # Can't rely on mock's call_count as it's not thread safe. fn_calls = [] def Fn(): fn_calls.append(True) event.wait() return self.mock_fn() decorated = cache.WithLimitedCallFrequency( rdfvalue.Duration.From(30, rdfvalue.SECONDS))(Fn) results = [] def T(): results.append(decorated()) threads = [] for _ in range(10): t = threading.Thread(target=T) t.start() threads.append(t) # At this point all threads should be waiting on the function to complete, # with only 1 threads actually executing the function. Trigger the event # to force that one thread to complete. event.set() for t in threads: t.join() self.assertLen(results, len(threads)) self.assertEqual(set(results), set([results[0]])) self.assertLen(fn_calls, 1)