blob: 33c82b273b536e238bd6c392e07acc077f525822 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import traceback
import unittest
from future import All, Future, Race
from mock_function import MockFunction
class FutureTest(unittest.TestCase):
def testNoValueOrDelegate(self):
self.assertRaises(ValueError, Future)
def testValue(self):
future = Future(value=42)
self.assertEqual(42, future.Get())
self.assertEqual(42, future.Get())
def testDelegateValue(self):
called = [False,]
def callback():
self.assertFalse(called[0])
called[0] = True
return 42
future = Future(callback=callback)
self.assertEqual(42, future.Get())
self.assertEqual(42, future.Get())
def testErrorThrowingDelegate(self):
class FunkyException(Exception):
pass
# Set up a chain of functions to test the stack trace.
def qux():
raise FunkyException()
def baz():
return qux()
def bar():
return baz()
def foo():
return bar()
chain = [foo, bar, baz, qux]
called = [False,]
def callback():
self.assertFalse(called[0])
called[0] = True
return foo()
fail = self.fail
assertTrue = self.assertTrue
def assert_raises_full_stack(future, err):
try:
future.Get()
fail('Did not raise %s' % err)
except Exception as e:
assertTrue(isinstance(e, err))
stack = traceback.format_exc()
assertTrue(all(stack.find(fn.__name__) != -1 for fn in chain))
future = Future(callback=callback)
assert_raises_full_stack(future, FunkyException)
assert_raises_full_stack(future, FunkyException)
def testAll(self):
def callback_with_value(value):
return MockFunction(lambda: value)
# Test a single value.
callback = callback_with_value(42)
future = All((Future(callback=callback),))
self.assertTrue(*callback.CheckAndReset(0))
self.assertEqual([42], future.Get())
self.assertTrue(*callback.CheckAndReset(1))
# Test multiple callbacks.
callbacks = (callback_with_value(1),
callback_with_value(2),
callback_with_value(3))
future = All(Future(callback=callback) for callback in callbacks)
for callback in callbacks:
self.assertTrue(*callback.CheckAndReset(0))
self.assertEqual([1, 2, 3], future.Get())
for callback in callbacks:
self.assertTrue(*callback.CheckAndReset(1))
# Test throwing an error.
def throws_error():
raise ValueError()
callbacks = (callback_with_value(1),
callback_with_value(2),
MockFunction(throws_error))
future = All(Future(callback=callback) for callback in callbacks)
for callback in callbacks:
self.assertTrue(*callback.CheckAndReset(0))
self.assertRaises(ValueError, future.Get)
for callback in callbacks:
# Can't check that the callbacks were actually run because in theory the
# Futures can be resolved in any order.
callback.CheckAndReset(0)
# Test throwing an error with except_pass.
future = All((Future(callback=callback) for callback in callbacks),
except_pass=ValueError)
for callback in callbacks:
self.assertTrue(*callback.CheckAndReset(0))
self.assertEqual([1, 2, None], future.Get())
def testRaceSuccess(self):
callback = MockFunction(lambda: 42)
# Test a single value.
race = Race((Future(callback=callback),))
self.assertTrue(*callback.CheckAndReset(0))
self.assertEqual(42, race.Get())
self.assertTrue(*callback.CheckAndReset(1))
# Test multiple success values. Note that we could test different values
# and check that the first returned, but this is just an implementation
# detail of Race. When we have parallel Futures this might not always hold.
race = Race((Future(callback=callback),
Future(callback=callback),
Future(callback=callback)))
self.assertTrue(*callback.CheckAndReset(0))
self.assertEqual(42, race.Get())
# Can't assert the actual count here for the same reason as above.
callback.CheckAndReset(99)
# Test values with except_pass.
def throws_error():
raise ValueError()
race = Race((Future(callback=callback),
Future(callback=throws_error)),
except_pass=(ValueError,))
self.assertTrue(*callback.CheckAndReset(0))
self.assertEqual(42, race.Get())
self.assertTrue(*callback.CheckAndReset(1))
def testRaceErrors(self):
def throws_error():
raise ValueError()
# Test a single error.
race = Race((Future(callback=throws_error),))
self.assertRaises(ValueError, race.Get)
# Test multiple errors. Can't use different error types for the same reason
# as described in testRaceSuccess.
race = Race((Future(callback=throws_error),
Future(callback=throws_error),
Future(callback=throws_error)))
self.assertRaises(ValueError, race.Get)
# Test values with except_pass.
def throws_except_error():
raise NotImplementedError()
race = Race((Future(callback=throws_error),
Future(callback=throws_except_error)),
except_pass=(NotImplementedError,))
self.assertRaises(ValueError, race.Get)
race = Race((Future(callback=throws_error),
Future(callback=throws_error)),
except_pass=(ValueError,))
self.assertRaises(ValueError, race.Get)
# Test except_pass with default values.
race = Race((Future(callback=throws_error),
Future(callback=throws_except_error)),
except_pass=(NotImplementedError,),
default=42)
self.assertRaises(ValueError, race.Get)
race = Race((Future(callback=throws_error),
Future(callback=throws_error)),
except_pass=(ValueError,),
default=42)
self.assertEqual(42, race.Get())
def testThen(self):
def assertIs42(val):
self.assertEqual(val, 42)
return val
then = Future(value=42).Then(assertIs42)
# Shouldn't raise an error.
self.assertEqual(42, then.Get())
# Test raising an error.
then = Future(value=41).Then(assertIs42)
self.assertRaises(AssertionError, then.Get)
# Test setting up an error handler.
def handle(error):
if isinstance(error, ValueError):
return 'Caught'
raise error
def raiseValueError():
raise ValueError
def raiseException():
raise Exception
then = Future(callback=raiseValueError).Then(assertIs42, handle)
self.assertEqual('Caught', then.Get())
then = Future(callback=raiseException).Then(assertIs42, handle)
self.assertRaises(Exception, then.Get)
# Test chains of thens.
addOne = lambda val: val + 1
then = Future(value=40).Then(addOne).Then(addOne).Then(assertIs42)
# Shouldn't raise an error.
self.assertEqual(42, then.Get())
# Test error in chain.
then = Future(value=40).Then(addOne).Then(assertIs42).Then(addOne)
self.assertRaises(AssertionError, then.Get)
# Test handle error in chain.
def raiseValueErrorWithVal(val):
raise ValueError
then = Future(value=40).Then(addOne).Then(raiseValueErrorWithVal).Then(
addOne, handle).Then(lambda val: val + ' me')
self.assertEquals(then.Get(), 'Caught me')
# Test multiple handlers.
def myHandle(error):
if isinstance(error, AssertionError):
return 10
raise error
then = Future(value=40).Then(assertIs42).Then(addOne, handle).Then(addOne,
myHandle)
self.assertEquals(then.Get(), 10)
def testThenResolvesReturnedFutures(self):
def returnsFortyTwo():
return Future(value=42)
def inc(x):
return x + 1
def incFuture(x):
return Future(value=x + 1)
self.assertEqual(43, returnsFortyTwo().Then(inc).Get())
self.assertEqual(43, returnsFortyTwo().Then(incFuture).Get())
self.assertEqual(44, returnsFortyTwo().Then(inc).Then(inc).Get())
self.assertEqual(44, returnsFortyTwo().Then(inc).Then(incFuture).Get())
self.assertEqual(44, returnsFortyTwo().Then(incFuture).Then(inc).Get())
self.assertEqual(
44, returnsFortyTwo().Then(incFuture).Then(incFuture).Get())
# The same behaviour should apply to error handlers.
def raisesSomething():
def boom(): raise ValueError
return Future(callback=boom)
def shouldNotHappen(_):
raise AssertionError()
def oops(error):
return 'oops'
def oopsFuture(error):
return Future(value='oops')
self.assertEqual(
'oops', raisesSomething().Then(shouldNotHappen, oops).Get())
self.assertEqual(
'oops', raisesSomething().Then(shouldNotHappen, oopsFuture).Get())
self.assertEqual(
'oops',
raisesSomething().Then(shouldNotHappen, raisesSomething)
.Then(shouldNotHappen, oops).Get())
self.assertEqual(
'oops',
raisesSomething().Then(shouldNotHappen, raisesSomething)
.Then(shouldNotHappen, oopsFuture).Get())
if __name__ == '__main__':
unittest.main()