Skip to content

Commit

Permalink
Spy expectations handle object arguments and exceptions properly.
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Milligan committed Jan 28, 2012
1 parent 232641e commit a1ae0d8
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Cedar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@
children = (
6628FC9B14C4DEC50016652A /* CDRSpy.mm */,
);
name = Doubles;
path = Doubles;
sourceTree = "<group>";
};
66F00B4F14C4D8F600146D88 /* Doubles */ = {
Expand Down
8 changes: 5 additions & 3 deletions Source/CDRSpy.mm → Source/Doubles/CDRSpy.mm
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ - (void)asOriginalObject:(void(^)())block {
Class spyClass = object_getClass(self);
object_setClass(self, objc_getAssociatedObject(self, @"original-class"));

block();

object_setClass(self, spyClass);
@try {
block();
} @finally {
object_setClass(self, spyClass);
}
}

@end
7 changes: 4 additions & 3 deletions Source/Headers/Doubles/Argument.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>
#import "CompareEqual.h"
#import "CedarStringifiers.h"
#import "CedarComparators.h"

namespace Cedar { namespace Doubles {

Expand Down Expand Up @@ -46,14 +47,14 @@ namespace Cedar { namespace Doubles {
/* virtual */ const char * TypedArgument<T>::value_encoding() const {
return @encode(T);
}

template<typename T>
/* virtual */ NSString * TypedArgument<T>::value_string() const {
return Matchers::Stringifiers::string_for(value_);
}

template<typename T>
/* virtual */ bool TypedArgument<T>::matches_bytes(void * expectedArgumentBytes) const {
return value_ == *(static_cast<T *>(expectedArgumentBytes));
return Matchers::Comparators::compare_equal(value_, *(static_cast<T *>(expectedArgumentBytes)));
}
}}
79 changes: 50 additions & 29 deletions Source/Headers/Doubles/HaveReceived.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ namespace Cedar { namespace Doubles {
// Allow default copy ctor.

template<typename T>
HaveReceived & with(const T & );

HaveReceived & with(const T &);
template<typename T>
HaveReceived & and_with(const T & argument) { return with(argument); }

Expand All @@ -24,8 +23,10 @@ namespace Cedar { namespace Doubles {
virtual NSString * failure_message_end() const;

private:
bool matches_invocation(NSInvocation * const invocation) const;
bool matches_arguments(NSInvocation * const invocation) const;
void verify_object_is_a_double(id) const;
bool matches_invocation(NSInvocation * const) const;
bool matches_arguments(NSInvocation * const) const;
void verify_correct_number_of_arguments(NSInvocation * const) const;

private:
const SEL expectedSelector_;
Expand All @@ -45,8 +46,8 @@ namespace Cedar { namespace Doubles {
return *this;
}

// I'd like to have this in a separate implementation file, but doing so generates
// an inscrutable compiler error:
// This belongs in a separate implementation file, but doing so generates an
// inscrutable linker error:
//
// ld: bad codegen, pointer diff in ___block_global_9 to global weak symbol
// __ZTVN5Cedar8Matchers4BaseINS0_18BaseMessageBuilderEEE for architecture i386
Expand All @@ -71,53 +72,73 @@ namespace Cedar { namespace Doubles {
arguments_.clear();
}

inline bool HaveReceived::matches(id instance) const {
this->verify_object_is_a_double(instance);

for (NSInvocation *invocation in [instance sent_messages]) {
if (this->matches_invocation(invocation)) {
return true;
}
}
return false;
}

inline void HaveReceived::verify_object_is_a_double(id instance) const {
if (![instance respondsToSelector:@selector(sent_messages)]) {
[[CDRSpecFailure specFailureWithReason:[NSString stringWithFormat:@"Received expectation for non-double object <%@>", instance]] raise];
}
}

#pragma mark Protected interface
inline /*virtual*/ NSString * HaveReceived::failure_message_end() const {
NSMutableString *message = [NSMutableString stringWithFormat:@"have received message <%@>", NSStringFromSelector(expectedSelector_)];
if (arguments_.size()) {
[message appendString:@", with arguments: <"];
for (arguments_vector_t::const_iterator cit = arguments_.begin(); cit != arguments_.end(); ++cit) {
[message appendString:(*cit)->value_string()];
arguments_vector_t::const_iterator cit = arguments_.begin();
[message appendString:(*cit++)->value_string()];
for (; cit != arguments_.end(); ++cit) {
[message appendString:[NSString stringWithFormat:@", %@", (*cit)->value_string()]];
}
[message appendString:@">"];
}
return message;
}

inline bool HaveReceived::matches(id instance) const {
for (NSInvocation *invocation in [instance sent_messages]) {
if (this->matches_invocation(invocation)) {
return true;
}
}
return false;
}

#pragma mark Private interface
inline bool HaveReceived::matches_invocation(NSInvocation * const invocation) const {
return sel_isEqual(invocation.selector, expectedSelector_) && this->matches_arguments(invocation);
}

inline bool HaveReceived::matches_arguments(NSInvocation * const invocation) const {
bool matches = true;
this->verify_correct_number_of_arguments(invocation);

bool matches = true;
size_t index = 2;
for (arguments_vector_t::const_iterator cit = arguments_.begin(); cit != arguments_.end() && matches; ++cit, ++index) {
const char *actualArgumentEncoding = [invocation.methodSignature getArgumentTypeAtIndex:index];
NSUInteger actualArgumentSize;
NSGetSizeAndAlignment(actualArgumentEncoding, &actualArgumentSize, nil);

void * actualArgumentBytes;
try {
actualArgumentBytes = malloc(actualArgumentSize);
[invocation getArgument:actualArgumentBytes atIndex:index];
matches = (*cit)->matches_bytes(actualArgumentBytes);
free(actualArgumentBytes);
}
catch (...) {
free(actualArgumentBytes);
throw;
}
char actualArgumentBytes[actualArgumentSize];
[invocation getArgument:&actualArgumentBytes atIndex:index];
matches = (*cit)->matches_bytes(&actualArgumentBytes);
}
return matches;
}

inline void HaveReceived::verify_correct_number_of_arguments(NSInvocation * const invocation) const {
size_t expectedArgumentCount = arguments_.size();
size_t actualArgumentCount = invocation.methodSignature.numberOfArguments - 2; //

if (expectedArgumentCount) {
if (expectedArgumentCount < actualArgumentCount) {
NSString *reason = [NSString stringWithFormat:@"Too few parameters expected for message <%@>; required %d, expected %d", NSStringFromSelector(expectedSelector_), actualArgumentCount, expectedArgumentCount];
[[CDRSpecFailure specFailureWithReason:reason] raise];
} else if (expectedArgumentCount > actualArgumentCount) {
NSString *reason = [NSString stringWithFormat:@"Too many parameters expected for message <%@>; required %d, expected %d", NSStringFromSelector(expectedSelector_), actualArgumentCount, expectedArgumentCount];
[[CDRSpecFailure specFailureWithReason:reason] raise];
}
}
}

}}
Loading

0 comments on commit a1ae0d8

Please sign in to comment.