2013-03-11 2 views
1

Я пытаюсь высмеять вызов mutableCopy с использованием OCMock и GHUnit на iOS.Неожиданный сбой с использованием OCMock, издевательский 'mutableCopy' на NSString

Несмотря на прохождение теста, во время очистки я получаю исключение EXC_BAD_ACCESS, и я пытаюсь понять, почему.

Взгляните на это. Этот тест показывает, что можно высмеять mutableCopy на макет NSString. В этом тесте я возвращаю еще NSString, а не NSMutableString. Это просто, чтобы продемонстрировать, что ожидание mutableCopy запущено, и тест проходит.

#import <GHUnitIOS/GHUnit.h> 
#import "OCMock.h" 

@interface TestItClass : GHTestCase @end 
@implementation TestItClass 

// Test that mutableCopy on an NSString is mockable. 
- (void)test_1_mutableCopyOfString_shouldBeMockable_givenAStringIsReturned { 
    NSString *string = [OCMockObject mockForClass:NSString.class]; 
    NSString *copy = @"foo"; 
    [(NSString *) [[(id) string expect] andReturn:copy] mutableCopy]; 

    // MutableCopy is mocked to return a string, not a mutable string! 
    // This is clearly wrong from a static typing point of view, but 
    // the test passes anyway, which is ok. 
    NSMutableString *result = [string mutableCopy]; 
    GHAssertEquals(result, copy, nil); 
    [(id)string verify]; 
} 

Теперь я могу изменить макет ожидания, так что mutableCopy теперь возвращает NSMutableString. Тест все еще проходит, но при срыве теста я получаю исключение EXC_BAD_ACCESS.

- (void)test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned { 
    NSString *string = [OCMockObject mockForClass:NSString.class]; 
    NSMutableString *copy = [@"foo" mutableCopy]; 
    [(NSString *) [[(id) string expect] andReturn:copy] mutableCopy]; 

    // Now mutableCopy is mocked to return a mutable string! 
    // The test now blows up during the test teardown! Why? 
    NSMutableString *foo = [string mutableCopy]; 
    GHAssertEquals(foo, copy, nil); 
    [(id)string verify]; 
} 

@end 

В обоих тестах проверяется работа, что касается утверждений. Это показывает, что оба теста хорошо сконструированы и что макетные ожидания увольняются, как ожидалось. Тем не менее, второй тест не в слезу вниз с плохим доступом к памяти:

Simulator session started with process 7496 
Debugger attached to process 7496 
2013-03-11 18:23:05.519 UnitTests[7496:c07] TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned ✘ 0.00s 
2013-03-11 18:23:06.466 UnitTests[7496:c07] Re-running: TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned <GHTest: 0x7793340> 
Exception: EXC_BAD_ACCESS (code=1, address=0x11dfe3ea)) 

Можете ли вы предложить мне, почему это может происходить?

Спасибо, Джо

+0

Пожалуйста, включите ваш '- (void) tearDown' methord в вопрос –

+0

Не существует явного tearDown; это только тот запас, который предоставляет GHUnit. –

+0

@Shineeth На самом деле, похоже, что авария находится в срыве одного из классов OCMock. Я разместил часть ответа ниже. –

ответ

2

проблема, с которой сталкивается ваша проблема, вызвана тем, что ARC следует за Basic Memory Management Rules. В частности, это:

  • Вы владеете какой-либо объект, вы создаете

    Вы создаете объект, используя метод, имя которого начинается с «Alloc», «новый», «копия», или «mutableCopy "(Например, alloc, newObject или mutableCopy).

Таким образом, решение будет смотреть на селекторе вызова, чтобы определить, следует ли retain в returnValue или нет.

Я надеюсь, что эта помощь.

+0

Это немного, что мне не хватало. Я рад, что мы обнаружили ошибку в OCMock, и сопровождающий исправил ее как следствие. Благодаря :). –

+1

Объяснение, предоставленное Зайграйвардом, является правильным; и OCMock неправильно вел себя для методов создания объектов. Теперь я реализовал решение. См. Https://github.com/erikdoe/ocmock/commit/00c0fe362de2b11625d348ae50379a108a3abf0b. –

0

Я часть пути к пониманию того, что происходит. Я скомпилировал библиотеку отладки OCMock, чтобы я мог понять, где происходит сбой.

Вот что я нашел.

В моем первоначальном тесте я называю andReturn:, чтобы установить значение возврата ожидание:

NSMutableString *copy = [@"foo" mutableCopy]; 
[(NSString *) [[(id) string expect] andReturn:copy] mutableCopy]; 

Это, в свою очередь, вызывает OCMReturnValueProvider хранить copy так, что он может возвращен в соответствующее время:

@implementation OCMReturnValueProvider 

- (id)initWithValue:(id)aValue 
{ 
    self = [super init]; 
    returnValue = [aValue retain]; 
    return self; 
} 

На этом этапе отладчик говорит, что aValue имеет тип __NSCFString. (Тревожные колокола звонят мне в голове, разве это не бесплатный мост для базовой строки? Не ссылка на NSMutableString)

Далее тест завершается и проходит.

Однако проблема возникает, когда OCMReturnValueProvider является dealloc 'd.

Аварийное происшествие происходит, когда вызывается [returnValue release]; OCMReturnValueProvider пытается освободить __NSCFString, что он retain ранее.

Далее, я имею переключатель на отладку NSZombie, что показательно:

2013-03-12 20:58:19.654 UnitTests[16667:c07] TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned 
2013-03-12 20:58:21.778 UnitTests[16667:c07] Re-running: TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned <GHTest: 0x4afc5fd0> 
2013-03-12 20:58:21.780 UnitTests[16667:c07] *** -[CFString release]: message sent to deallocated instance 0x4b0b1fe0 

таНос-история (Найти Zombie инструмент) помогает пролить некоторый свет на это:

Category   Event Type Ref Ct Responsible Caller 
CFString (mutable) Malloc  1  -[TestItClass test_2_mutable...] 
CFString (mutable) Retain  2  -[OCMReturnValueProvider initWithValue:] 
CFString (mutable) Retain  3  -[TestItClass test_2_mutable...] 
CFString (mutable) Retain  4  -[TestItClass test_2_mutable...] 
CFString (mutable) Release  3  -[TestItClass test_2_mutable...] 
CFString (mutable) Release  2  -[TestItClass test_2_mutable...] 
CFString (mutable) Release  1  -[TestItClass test_2_mutable...] 
CFString (mutable) Release  0  -[TestItClass test_2_mutable...] 
CFString (mutable) Zombie  -1  -[OCMReturnValueProvider dealloc] 

Так что-то в классе тестирования вызывает больше выпусков, чем сохраняется. Почему это происходит? Странный!

0

После нескольких дополнительных исследований я обнаружил, почему происходит сбой.

Давайте посмотрим на тест еще раз:

- (void)test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned { 
    NSString *string = [OCMockObject mockForClass:NSString.class]; 
    NSMutableString *copy = [@"foo" mutableCopy]; 
    [(NSString *) [[(id) string expect] andReturn:copy] mutableCopy]; 

    NSMutableString *foo = [string mutableCopy]; 
} 

Что происходит в том, что компилятор предполагает, что объект, возвращаемый [string mutableCopy] это был retained по mutableCopy, и поэтому, когда foo является dealloced ARC делает эквивалент [foo release]. Это проблема, потому что у этого объекта у него не было счетчика ссылок, увеличенного в пределах andReturn:.

Я смущен тем, почему мы не видим этого поведения с другими объектами, настроенными на возврат andReturn:. OCMReturnValueProvider обработки высмеивала ответ не ARC удались, и не сохраняя возвращаемое значения:

- (void)handleInvocation:(NSInvocation *)anInvocation 
{ 
    [anInvocation setReturnValue:&returnValue]; 
} 

Таким образом, проблема просто решается с помощью упреждающего retain ИНГ возвращаемого значения перед установкой его в NSInvocation:

- (void)handleInvocation:(NSInvocation *)anInvocation 
{ 
    [returnValue retain]; 
    [anInvocation setReturnValue:&returnValue]; 
} 

Это похоже на ошибку в OCMock. Но, учитывая, что проблема не возникает при любых обстоятельствах, я точно не знаю. Мое исправление работает, но теперь существует риск утечки памяти на объекты, которые могут не нуждаться в дополнительных retain. Тем не менее, утечка памяти в тесте, а не тест, который не запускается, для меня сейчас приемлема.

+0

Я предложил это исправление разработчику OCMock. Будет отчитываться здесь, как только он будет разрешен. –

+0

Я считаю, что проблема в том, что mutableCopy несколько особенный. Обычно абонент должен сохранять объекты, которые они хотят сохранить. Вызываемый метод возвращает объект с «нейтральным» счетом сохранения. Однако MutableCopy возвращает объект со значением сохранения +1. Я увидел запрос на pull, но попытаюсь найти решение, которое не будет утечки памяти. –

+0

Объяснение, предоставленное Зайграйвардом, является правильным; и OCMock неправильно вел себя для методов создания объектов. Теперь я реализовал решение. См. Https://github.com/erikdoe/ocmock/commit/00c0fe362de2b11625d348ae50379a108a3abf0b. –

Смежные вопросы