summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsmiddlek <smiddlek@b1010a0a-674b-0410-b734-77272b80c875>2012-05-01 20:43:51 +0000
committersmiddlek <smiddlek@b1010a0a-674b-0410-b734-77272b80c875>2012-05-01 20:43:51 +0000
commitf2b16354e6fd07044d86149fb2f2e7f321574c65 (patch)
treeb9325ac91f6635d0635b98551770d1f3239b03f2
parentdf220ffcc57380c3823aae52cdea0d7bb6f2b145 (diff)
downloadmox-f2b16354e6fd07044d86149fb2f2e7f321574c65.tar.gz
Patch for Issue 16 from Ben Love. Thanks!
git-svn-id: http://pymox.googlecode.com/svn/trunk@72 b1010a0a-674b-0410-b734-77272b80c875
-rwxr-xr-xmox.py106
-rwxr-xr-xmox_test.py179
2 files changed, 233 insertions, 52 deletions
diff --git a/mox.py b/mox.py
index 2ffdf90..9c919c9 100755
--- a/mox.py
+++ b/mox.py
@@ -224,6 +224,30 @@ class UnexpectedMockCreationError(Error):
return error
+class SwallowedExceptionError(Error):
+ """Raised when Verify() is called after something already threw an exception.
+
+ This means that the exception that was thrown was somehow swallowed, allowing
+ the test to continue when it should not have.
+ """
+
+ def __init__(self, previous_exceptions):
+ """Init exception.
+
+ Args:
+ # previous_exceptions: A sequence of Error objects that were raised.
+ previous_exceptions: [Error]
+ """
+
+ Error.__init__(self)
+ self._previous_exceptions = previous_exceptions
+
+ def __str__(self):
+ exceptions = "\n".join(["%3d. %s: %s" % (i, e.__class__.__name__, e)
+ for i, e in enumerate(self._previous_exceptions)])
+ return "Previous exceptions thrown:\n%s" % (exceptions,)
+
+
class Mox(object):
"""Mox: a factory for creating mock objects."""
@@ -426,8 +450,18 @@ class MockAnything:
created, for debugging output purposes.
"""
self._description = description
+ self._exceptions_thrown = []
self._Reset()
+ def __bases__(self):
+ pass
+
+ def __members__(self):
+ pass
+
+ def __methods__(self):
+ pass
+
def __repr__(self):
if self._description:
return '<MockAnything instance of %s>' % self._description
@@ -467,7 +501,8 @@ class MockAnything:
"""
return MockMethod(method_name, self._expected_calls_queue,
- self._replay_mode, method_to_mock=method_to_mock,
+ self._exceptions_thrown, self._replay_mode,
+ method_to_mock=method_to_mock,
description=self._description)
def __nonzero__(self):
@@ -498,8 +533,15 @@ class MockAnything:
Raises:
ExpectedMethodCallsError: if there are still more method calls in the
expected queue.
+ any exception previously raised by this object: if _Verify was called
+ afterwards anyway. (This detects tests passing erroneously.)
"""
+ # If any exceptions were thrown, re-raise them. (This should only happen
+ # if the original exception was swallowed, in which case it's necessary to
+ # re-raise it so that the test will fail. See Issue #16.)
+ if self._exceptions_thrown:
+ raise SwallowedExceptionError(self._exceptions_thrown)
# If the list of expected calls is not empty, raise an exception
if self._expected_calls_queue:
# The last MultipleTimesGroup is not popped from the queue.
@@ -610,7 +652,9 @@ class MockObject(MockAnything, object):
name,
method_to_mock=getattr(self._class_to_mock, name))
- raise UnknownMethodCallError(name)
+ exception = UnknownMethodCallError(name)
+ self._exceptions_thrown.append(exception)
+ raise exception
def __eq__(self, rhs):
"""Provide custom logic to compare objects."""
@@ -644,7 +688,7 @@ class MockObject(MockAnything, object):
# If we are in replay mode then simply call the mock __setitem__ method.
if self._replay_mode:
return MockMethod('__setitem__', self._expected_calls_queue,
- self._replay_mode)(key, value)
+ self._exceptions_thrown, self._replay_mode)(key, value)
# Otherwise, create a mock method __setitem__.
@@ -673,7 +717,7 @@ class MockObject(MockAnything, object):
# If we are in replay mode then simply call the mock __getitem__ method.
if self._replay_mode:
return MockMethod('__getitem__', self._expected_calls_queue,
- self._replay_mode)(key)
+ self._exceptions_thrown, self._replay_mode)(key)
# Otherwise, create a mock method __getitem__.
@@ -713,7 +757,7 @@ class MockObject(MockAnything, object):
# If we are in replay mode then simply call the mock __iter__ method.
if self._replay_mode:
return MockMethod('__iter__', self._expected_calls_queue,
- self._replay_mode)()
+ self._exceptions_thrown, self._replay_mode)()
# Otherwise, create a mock method __iter__.
@@ -743,7 +787,7 @@ class MockObject(MockAnything, object):
if self._replay_mode:
return MockMethod('__contains__', self._expected_calls_queue,
- self._replay_mode)(key)
+ self._exceptions_thrown, self._replay_mode)(key)
return self._CreateMockMethod('__contains__')(key)
@@ -809,8 +853,10 @@ class _MockObjectFactory(MockObject):
if self._replay_mode:
if not self._instance_queue:
- raise UnexpectedMockCreationError(self._class_to_mock, *params,
- **named_params)
+ exception = UnexpectedMockCreationError(self._class_to_mock, *params,
+ **named_params)
+ self._exceptions_thrown.append(exception)
+ raise exception
mock_method(*params, **named_params)
@@ -967,7 +1013,7 @@ class MockMethod(object):
signature) matches the expected method.
"""
- def __init__(self, method_name, call_queue, replay_mode,
+ def __init__(self, method_name, call_queue, exception_list, replay_mode,
method_to_mock=None, description=None):
"""Construct a new mock method.
@@ -975,6 +1021,8 @@ class MockMethod(object):
# method_name: the name of the method
# call_queue: deque of calls, verify this call against the head, or add
# this call to the queue.
+ # exception_list: list of exceptions; any exceptions thrown by this
+ # instance are appended to this list.
# replay_mode: False if we are recording, True if we are verifying calls
# against the call queue.
# method_to_mock: The actual method being mocked, used for introspection.
@@ -982,6 +1030,7 @@ class MockMethod(object):
# this is equal to the descriptive name of the method's class.
method_name: str
call_queue: list or deque
+ exception_list: list
replay_mode: bool
method_to_mock: a method object
description: str or None
@@ -992,6 +1041,7 @@ class MockMethod(object):
self._call_queue = call_queue
if not isinstance(call_queue, deque):
self._call_queue = deque(self._call_queue)
+ self._exception_list = exception_list
self._replay_mode = replay_mode
self._description = description
@@ -1061,7 +1111,9 @@ class MockMethod(object):
try:
return self._call_queue.popleft()
except IndexError:
- raise UnexpectedMethodCallError(self, None)
+ exception = UnexpectedMethodCallError(self, None)
+ self._exception_list.append(exception)
+ raise exception
def _VerifyMethodCall(self):
"""Verify the called method is expected.
@@ -1086,7 +1138,9 @@ class MockMethod(object):
# This is a mock method, so just check equality.
if expected != self:
- raise UnexpectedMethodCallError(self, expected)
+ exception = UnexpectedMethodCallError(self, expected)
+ self._exception_list.append(exception)
+ raise exception
return expected
@@ -1158,7 +1212,7 @@ class MockMethod(object):
return self
# Create a new group and add the method.
- new_group = group_class(group_name)
+ new_group = group_class(group_name, self._exception_list)
new_group.AddMethod(self)
self._call_queue.append(new_group)
return self
@@ -1831,8 +1885,18 @@ class Remember(Comparator):
class MethodGroup(object):
"""Base class containing common behaviour for MethodGroups."""
- def __init__(self, group_name):
+ def __init__(self, group_name, exception_list):
+ """Construct a new method group.
+
+ Args:
+ # group_name: the name of the method group
+ # exception_list: list of exceptions; any exceptions thrown by this
+ # instance are appended to this list.
+ group_name: str
+ exception_list: list
+ """
self._group_name = group_name
+ self._exception_list = exception_list
def group_name(self):
return self._group_name
@@ -1856,8 +1920,8 @@ class UnorderedGroup(MethodGroup):
over the keys of a dict.
"""
- def __init__(self, group_name):
- super(UnorderedGroup, self).__init__(group_name)
+ def __init__(self, group_name, exception_list):
+ super(UnorderedGroup, self).__init__(group_name, exception_list)
self._methods = []
def __str__(self):
@@ -1907,7 +1971,9 @@ class UnorderedGroup(MethodGroup):
return self, method
- raise UnexpectedMethodCallError(mock_method, self)
+ exception = UnexpectedMethodCallError(mock_method, self)
+ self._exception_list.append(exception)
+ raise exception
def IsSatisfied(self):
"""Return True if there are not any methods in this group."""
@@ -1923,8 +1989,8 @@ class MultipleTimesGroup(MethodGroup):
This is helpful, if you don't know or care how many times a method is called.
"""
- def __init__(self, group_name):
- super(MultipleTimesGroup, self).__init__(group_name)
+ def __init__(self, group_name, exception_list):
+ super(MultipleTimesGroup, self).__init__(group_name, exception_list)
self._methods = set()
self._methods_left = set()
@@ -1968,7 +2034,9 @@ class MultipleTimesGroup(MethodGroup):
next_method = mock_method._PopNextMethod();
return next_method, None
else:
- raise UnexpectedMethodCallError(mock_method, self)
+ exception = UnexpectedMethodCallError(mock_method, self)
+ self._exception_list.append(exception)
+ raise exception
def IsSatisfied(self):
"""Return True if all methods in this group are called at least once."""
diff --git a/mox_test.py b/mox_test.py
index f865fb9..d489f0b 100755
--- a/mox_test.py
+++ b/mox_test.py
@@ -33,7 +33,7 @@ class ExpectedMethodCallsErrorTest(unittest.TestCase):
self.assertRaises(ValueError, mox.ExpectedMethodCallsError, [])
def testOneError(self):
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method(1, 2).AndReturn('output')
e = mox.ExpectedMethodCallsError([method])
self.assertEqual(
@@ -42,13 +42,13 @@ class ExpectedMethodCallsErrorTest(unittest.TestCase):
str(e))
def testManyErrors(self):
- method1 = mox.MockMethod("testMethod", [], False)
+ method1 = mox.MockMethod("testMethod", [], [], False)
method1(1, 2).AndReturn('output')
- method2 = mox.MockMethod("testMethod", [], False)
+ method2 = mox.MockMethod("testMethod", [], [], False)
method2(a=1, b=2, c="only named")
- method3 = mox.MockMethod("testMethod2", [], False)
+ method3 = mox.MockMethod("testMethod2", [], [], False)
method3().AndReturn(44)
- method4 = mox.MockMethod("testMethod", [], False)
+ method4 = mox.MockMethod("testMethod", [], [], False)
method4(1, 2).AndReturn('output')
e = mox.ExpectedMethodCallsError([method1, method2, method3, method4])
self.assertEqual(
@@ -457,8 +457,9 @@ class MockMethodTest(unittest.TestCase):
"""Test class to verify that the MockMethod class is working correctly."""
def setUp(self):
- self.expected_method = mox.MockMethod("testMethod", [], False)(['original'])
- self.mock_method = mox.MockMethod("testMethod", [self.expected_method],
+ self.expected_method = mox.MockMethod("testMethod", [], [], False)(
+ ['original'])
+ self.mock_method = mox.MockMethod("testMethod", [self.expected_method], [],
True)
def testNameAttribute(self):
@@ -523,18 +524,18 @@ class MockMethodTest(unittest.TestCase):
def testEqualityNoParamsEqual(self):
"""Methods with the same name and without params should be equal."""
- expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method = mox.MockMethod("testMethod", [], [], False)
self.assertEqual(self.mock_method, expected_method)
def testEqualityNoParamsNotEqual(self):
"""Methods with different names and without params should not be equal."""
- expected_method = mox.MockMethod("otherMethod", [], False)
+ expected_method = mox.MockMethod("otherMethod", [], [], False)
self.failIfEqual(self.mock_method, expected_method)
def testEqualityParamsEqual(self):
"""Methods with the same name and parameters should be equal."""
params = [1, 2, 3]
- expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method = mox.MockMethod("testMethod", [], [], False)
expected_method._params = params
self.mock_method._params = params
@@ -542,7 +543,7 @@ class MockMethodTest(unittest.TestCase):
def testEqualityParamsNotEqual(self):
"""Methods with the same name and different params should not be equal."""
- expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method = mox.MockMethod("testMethod", [], [], False)
expected_method._params = [1, 2, 3]
self.mock_method._params = ['a', 'b', 'c']
@@ -551,7 +552,7 @@ class MockMethodTest(unittest.TestCase):
def testEqualityNamedParamsEqual(self):
"""Methods with the same name and same named params should be equal."""
named_params = {"input1": "test", "input2": "params"}
- expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method = mox.MockMethod("testMethod", [], [], False)
expected_method._named_params = named_params
self.mock_method._named_params = named_params
@@ -559,7 +560,7 @@ class MockMethodTest(unittest.TestCase):
def testEqualityNamedParamsNotEqual(self):
"""Methods with the same name and diffnamed params should not be equal."""
- expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method = mox.MockMethod("testMethod", [], [], False)
expected_method._named_params = {"input1": "test", "input2": "params"}
self.mock_method._named_params = {"input1": "test2", "input2": "params2"}
@@ -575,39 +576,39 @@ class MockMethodTest(unittest.TestCase):
instB = TestClass();
params = [instA, ]
- expected_method = mox.MockMethod("testMethod", [], False)
+ expected_method = mox.MockMethod("testMethod", [], [], False)
expected_method._params = params
self.mock_method._params = [instB, ]
self.assertEqual(self.mock_method, expected_method)
def testStrConversion(self):
- method = mox.MockMethod("f", [], False)
+ method = mox.MockMethod("f", [], [], False)
method(1, 2, "st", n1=8, n2="st2")
self.assertEqual(str(method), ("f(1, 2, 'st', n1=8, n2='st2') -> None"))
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method(1, 2, "only positional")
self.assertEqual(str(method), "testMethod(1, 2, 'only positional') -> None")
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method(a=1, b=2, c="only named")
self.assertEqual(str(method),
"testMethod(a=1, b=2, c='only named') -> None")
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method()
self.assertEqual(str(method), "testMethod() -> None")
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method(x="only 1 parameter")
self.assertEqual(str(method), "testMethod(x='only 1 parameter') -> None")
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method().AndReturn('return_value')
self.assertEqual(str(method), "testMethod() -> 'return_value'")
- method = mox.MockMethod("testMethod", [], False)
+ method = mox.MockMethod("testMethod", [], [], False)
method().AndReturn(('a', {1: 2}))
self.assertEqual(str(method), "testMethod() -> ('a', {1: 2})")
@@ -746,7 +747,7 @@ class MethodCheckerTest(unittest.TestCase):
"""Tests MockMethod's use of MethodChecker method."""
def testNoParameters(self):
- method = mox.MockMethod('NoParameters', [], False,
+ method = mox.MockMethod('NoParameters', [], [], False,
CheckCallTestClass.NoParameters)
method()
self.assertRaises(AttributeError, method, 1)
@@ -755,7 +756,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, 1, b=2)
def testOneParameter(self):
- method = mox.MockMethod('OneParameter', [], False,
+ method = mox.MockMethod('OneParameter', [], [], False,
CheckCallTestClass.OneParameter)
self.assertRaises(AttributeError, method)
method(1)
@@ -766,7 +767,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, 1, b=2)
def testTwoParameters(self):
- method = mox.MockMethod('TwoParameters', [], False,
+ method = mox.MockMethod('TwoParameters', [], [], False,
CheckCallTestClass.TwoParameters)
self.assertRaises(AttributeError, method)
self.assertRaises(AttributeError, method, 1)
@@ -783,7 +784,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, 3, a=1, b=2)
def testOneDefaultValue(self):
- method = mox.MockMethod('OneDefaultValue', [], False,
+ method = mox.MockMethod('OneDefaultValue', [], [], False,
CheckCallTestClass.OneDefaultValue)
method()
method(1)
@@ -794,7 +795,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, 1, b=2)
def testTwoDefaultValues(self):
- method = mox.MockMethod('TwoDefaultValues', [], False,
+ method = mox.MockMethod('TwoDefaultValues', [], [], False,
CheckCallTestClass.TwoDefaultValues)
self.assertRaises(AttributeError, method)
self.assertRaises(AttributeError, method, c=3)
@@ -814,7 +815,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, a=1, b=2, e=9)
def testArgs(self):
- method = mox.MockMethod('Args', [], False, CheckCallTestClass.Args)
+ method = mox.MockMethod('Args', [], [], False, CheckCallTestClass.Args)
self.assertRaises(AttributeError, method)
self.assertRaises(AttributeError, method, 1)
method(1, 2)
@@ -825,7 +826,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, 1, 2, c=3)
def testKwargs(self):
- method = mox.MockMethod('Kwargs', [], False, CheckCallTestClass.Kwargs)
+ method = mox.MockMethod('Kwargs', [], [], False, CheckCallTestClass.Kwargs)
self.assertRaises(AttributeError, method)
method(1)
method(1, 2)
@@ -840,7 +841,7 @@ class MethodCheckerTest(unittest.TestCase):
self.assertRaises(AttributeError, method, 1, 2, 3, 4)
def testArgsAndKwargs(self):
- method = mox.MockMethod('ArgsAndKwargs', [], False,
+ method = mox.MockMethod('ArgsAndKwargs', [], [], False,
CheckCallTestClass.ArgsAndKwargs)
self.assertRaises(AttributeError, method)
method(1)
@@ -1031,7 +1032,7 @@ class MockObjectTest(unittest.TestCase):
self.assertRaises(mox.UnexpectedMethodCallError, call)
- dummy._Verify()
+ self.assertRaises(mox.SwallowedExceptionError, dummy._Verify)
def testMockSetItem_WithSubClassOfNewStyleClass(self):
class NewStyleTestClass(object):
@@ -1099,7 +1100,7 @@ class MockObjectTest(unittest.TestCase):
self.assertRaises(mox.UnexpectedMethodCallError, call)
- dummy._Verify()
+ self.assertRaises(mox.SwallowedExceptionError, dummy._Verify)
def testMockGetItem_WithSubClassOfNewStyleClass(self):
class NewStyleTestClass(object):
@@ -1169,7 +1170,7 @@ class MockObjectTest(unittest.TestCase):
self.assertRaises(mox.UnexpectedMethodCallError, call)
- dummy._Verify()
+ self.assertRaises(mox.SwallowedExceptionError, dummy._Verify)
def testMockIter_ExpectedIter_NoSuccess(self):
"""Test that __iter__() gets mocked in Dummy.
@@ -1711,7 +1712,7 @@ class MoxTest(unittest.TestCase):
self.assertRaises(mox.UnexpectedMethodCallError,
TestClass.OtherValidCall, "wrong self")
- self.mox.VerifyAll()
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
self.mox.UnsetStubs()
def testStubOutMethod_Unbound_NamedUsingPositional(self):
@@ -2001,6 +2002,118 @@ class MoxTest(unittest.TestCase):
self.assertEquals('MockMethod has no attribute "ShowMeTheMoney". '
'Did you remember to put your mocks in replay mode?', str(e))
+ def testSwallowedUnknownMethodCall(self):
+ """Test that a swallowed UnknownMethodCallError will be re-raised."""
+ dummy = self.mox.CreateMock(TestClass)
+ dummy._Replay()
+
+ def call():
+ try:
+ dummy.InvalidCall()
+ except mox.UnknownMethodCallError:
+ pass
+
+ # UnknownMethodCallError swallowed
+ call()
+
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
+
+ def testSwallowedUnexpectedMockCreation(self):
+ """Test that a swallowed UnexpectedMockCreationError will be re-raised."""
+ self.mox.StubOutClassWithMocks(mox_test_helper, 'CallableClass')
+ self.mox.ReplayAll()
+
+ def call():
+ try:
+ mox_test_helper.CallableClass(1, 2)
+ except mox.UnexpectedMockCreationError:
+ pass
+
+ # UnexpectedMockCreationError swallowed
+ call()
+
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
+ self.mox.UnsetStubs()
+
+ def testSwallowedUnexpectedMethodCall_WrongMethod(self):
+ """Test that a swallowed UnexpectedMethodCallError will be re-raised.
+
+ This case is an extraneous method call."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ self.mox.ReplayAll()
+
+ def call():
+ mock_obj.Open()
+ try:
+ mock_obj.Close()
+ except mox.UnexpectedMethodCallError:
+ pass
+
+ # UnexpectedMethodCall swallowed
+ call()
+
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
+
+ def testSwallowedUnexpectedMethodCall_WrongArguments(self):
+ """Test that a swallowed UnexpectedMethodCallError will be re-raised.
+
+ This case is an extraneous method call."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open()
+ self.mox.ReplayAll()
+
+ def call():
+ try:
+ mock_obj.Open(1)
+ except mox.UnexpectedMethodCallError:
+ pass
+
+ # UnexpectedMethodCall swallowed
+ call()
+
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
+
+ def testSwallowedUnexpectedMethodCall_UnorderedGroup(self):
+ """Test that a swallowed UnexpectedMethodCallError will be re-raised.
+
+ This case is an extraneous method call in an unordered group."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open().InAnyOrder()
+ mock_obj.Close().InAnyOrder()
+ self.mox.ReplayAll()
+
+ def call():
+ mock_obj.Close()
+ try:
+ mock_obj.Open(1)
+ except mox.UnexpectedMethodCallError:
+ pass
+
+ # UnexpectedMethodCall swallowed
+ call()
+
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
+
+ def testSwallowedUnexpectedMethodCall_MultipleTimesGroup(self):
+ """Test that a swallowed UnexpectedMethodCallError will be re-raised.
+
+ This case is an extraneous method call in a multiple times group."""
+ mock_obj = self.mox.CreateMockAnything()
+ mock_obj.Open().MultipleTimes()
+ self.mox.ReplayAll()
+
+ def call():
+ try:
+ mock_obj.Open(1)
+ except mox.UnexpectedMethodCallError:
+ pass
+
+ # UnexpectedMethodCall swallowed
+ call()
+
+ self.assertRaises(mox.SwallowedExceptionError, self.mox.VerifyAll)
+
class ReplayTest(unittest.TestCase):
"""Verify Replay works properly."""