123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- //
- // Licensed under the terms in License.txt
- //
- // Copyright 2010 Allen Ding. All rights reserved.
- //
- #import "KWExample.h"
- #import "KWExampleSuiteBuilder.h"
- #import "KWContextNode.h"
- #import "KWMatcherFactory.h"
- #import "KWExistVerifier.h"
- #import "KWMatchVerifier.h"
- #import "KWAsyncVerifier.h"
- #import "KWFailure.h"
- #import "KWContextNode.h"
- #import "KWBeforeEachNode.h"
- #import "KWBeforeAllNode.h"
- #import "KWLetNode.h"
- #import "KWItNode.h"
- #import "KWAfterEachNode.h"
- #import "KWAfterAllNode.h"
- #import "KWPendingNode.h"
- #import "KWRegisterMatchersNode.h"
- #import "KWWorkarounds.h"
- #import "KWIntercept.h"
- #import "KWExampleNode.h"
- #import "KWExampleSuite.h"
- #import "KWCallSite.h"
- #import "KWSymbolicator.h"
- @interface KWExample ()
- @property (nonatomic, readonly) NSMutableArray *verifiers;
- @property (nonatomic, readonly) KWMatcherFactory *matcherFactory;
- @property (nonatomic, weak) XCTestCase<KWExampleDelegate> *delegate;
- @property (nonatomic, assign) BOOL didNotFinish;
- @property (nonatomic, strong) id<KWExampleNode> exampleNode;
- @property (nonatomic, assign) BOOL passed;
- - (void)reportResultForExampleNodeWithLabel:(NSString *)label;
- @end
- @implementation KWExample
- @synthesize selectorName = _selectorName;
- - (id)initWithExampleNode:(id<KWExampleNode>)node {
- self = [super init];
- if (self) {
- _exampleNode = node;
- _matcherFactory = [[KWMatcherFactory alloc] init];
- _verifiers = [[NSMutableArray alloc] init];
- _lastInContexts = [[NSMutableArray alloc] init];
- _passed = YES;
- }
- return self;
- }
- - (BOOL)isLastInContext:(KWContextNode *)context {
- for (KWContextNode *contextWhereItLast in self.lastInContexts) {
- if (context == contextWhereItLast) {
- return YES;
- }
- }
- return NO;
- }
- - (NSString *)description {
- return [NSString stringWithFormat:@"<KWExample: %@>", self.exampleNode.description];
- }
- #pragma mark - Message forwarding
- - (id)forwardingTargetForSelector:(SEL)aSelector {
- if ([self.delegate respondsToSelector:aSelector]) {
- return self.delegate;
- } else {
- return [super forwardingTargetForSelector:aSelector];
- }
- }
- - (BOOL)respondsToSelector:(SEL)aSelector {
- return [super respondsToSelector:aSelector] || [self.delegate respondsToSelector:aSelector];
- }
- #pragma mark - Adding Verifiers
- - (id)addVerifier:(id<KWVerifying>)aVerifier {
- if (![self.verifiers containsObject:aVerifier])
- [self.verifiers addObject:aVerifier];
-
- return aVerifier;
- }
- - (id)addExistVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite {
- id verifier = [KWExistVerifier existVerifierWithExpectationType:anExpectationType callSite:aCallSite reporter:self];
- [self addVerifier:verifier];
- return verifier;
- }
- - (id)addMatchVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite {
- if (self.unresolvedVerifier) {
- KWFailure *failure = [KWFailure failureWithCallSite:self.unresolvedVerifier.callSite format:@"expected subject not to be nil"];
- [self reportFailure:failure];
- }
- id<KWVerifying> verifier = [KWMatchVerifier matchVerifierWithExpectationType:anExpectationType callSite:aCallSite matcherFactory:self.matcherFactory reporter:self];
- [self addVerifier:verifier];
- self.unresolvedVerifier = verifier;
- return verifier;
- }
- - (id)addAsyncVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite timeout:(NSTimeInterval)timeout shouldWait:(BOOL)shouldWait {
- id verifier = [KWAsyncVerifier asyncVerifierWithExpectationType:anExpectationType callSite:aCallSite matcherFactory:self.matcherFactory reporter:self probeTimeout:timeout shouldWait: shouldWait];
- [self addVerifier:verifier];
- return verifier;
- }
- #pragma mark - Clear Verifiers
- - (void)clearVerifiers {
- [self.verifiers removeAllObjects];
- }
- #pragma mark - Running examples
- - (void)runWithDelegate:(XCTestCase<KWExampleDelegate> *)delegate {
- self.delegate = delegate;
- [self.matcherFactory registerMatcherClassesWithNamespacePrefix:@"KW"];
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] setCurrentExample:self];
- [self.exampleNode acceptExampleNodeVisitor:self];
- [self clearVerifiers];
- }
- #pragma mark - Reporting failure
- - (NSString *)descriptionForExampleContext {
- NSMutableArray *parts = [NSMutableArray array];
-
- for (KWContextNode *context in [[self.exampleNode contextStack] reverseObjectEnumerator]) {
- if ([context description] != nil) {
- [parts addObject:[[context description] stringByAppendingString:@","]];
- }
- }
-
- return [parts componentsJoinedByString:@" "];
- }
- - (KWFailure *)outputReadyFailureWithFailure:(KWFailure *)aFailure {
- NSString *annotatedFailureMessage = [NSString stringWithFormat:@"'%@ %@' [FAILED], %@",
- [self descriptionForExampleContext], [self.exampleNode description],
- aFailure.message];
-
- #if TARGET_IPHONE_SIMULATOR
- // \uff1a is the unicode for a fill width colon, as opposed to a regular
- // colon character (':'). This escape is performed so that Xcode doesn't
- // truncate the error output in the build results window, which is running
- // build time specs.
- annotatedFailureMessage = [annotatedFailureMessage stringByReplacingOccurrencesOfString:@":" withString:@"\uff1a"];
- #endif // #if TARGET_IPHONE_SIMULATOR
-
- return [KWFailure failureWithCallSite:aFailure.callSite message:annotatedFailureMessage];
- }
- - (void)reportFailure:(KWFailure *)failure {
- self.passed = NO;
- [self.delegate example:self didFailWithFailure:[self outputReadyFailureWithFailure:failure]];
- }
- - (void)reportResultForExampleNodeWithLabel:(NSString *)label {
- NSLog(@"+ '%@ %@' [%@]", [self descriptionForExampleContext], [self.exampleNode description], label);
- }
- #pragma mark - Full description with context
- /** Pending cases will be marked yellow by XCode as not finished, because their description differs for -[SenTestCaseRun start] and -[SenTestCaseRun stop] methods
- */
- - (NSString *)pendingNotFinished {
- BOOL reportPending = self.didNotFinish;
- self.didNotFinish = YES;
- return reportPending ? @"(PENDING)" : @"";
- }
-
- - (NSString *)descriptionWithContext {
- NSString *descriptionWithContext = [NSString stringWithFormat:@"%@ %@",
- [self descriptionForExampleContext],
- [self.exampleNode description] ? [self.exampleNode description] : @""];
- BOOL isPending = [self.exampleNode isKindOfClass:[KWPendingNode class]];
- return isPending ? [descriptionWithContext stringByAppendingString:[self pendingNotFinished]] : descriptionWithContext;
- }
- - (NSString *)selectorName {
- if (_selectorName) {
- return _selectorName;
- }
- NSString *name = [self descriptionWithContext];
- // CamelCase the string
- NSArray *words = [name componentsSeparatedByString:@" "];
- name = @"";
- for (NSString *word in words) {
- if ([word length] < 1)
- {
- continue;
- }
- name = [name stringByAppendingString:[[word substringToIndex:1] uppercaseString]];
- name = [name stringByAppendingString:[word substringFromIndex:1]];
- }
- // Replace the commas with underscores to separate the levels of context
- name = [name stringByReplacingOccurrencesOfString:@"," withString:@"_"];
- // Strip out characters not legal in function names
- NSError *error = nil;
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^a-zA-Z0-9_]*" options:0 error:&error];
- name = [regex stringByReplacingMatchesInString:name options:0 range:NSMakeRange(0, name.length) withTemplate:@""];
- // Ensure examples in the same suite have unique selector names
- if (self.suite) {
- name = [self.suite nextUniqueSelectorName:name];
- }
- return (_selectorName = name);
- }
- #pragma mark - Visiting Nodes
- - (void)visitRegisterMatchersNode:(KWRegisterMatchersNode *)aNode {
- [self.matcherFactory registerMatcherClassesWithNamespacePrefix:aNode.namespacePrefix];
- }
- - (void)visitBeforeAllNode:(KWBeforeAllNode *)aNode {
- if (aNode.block == nil)
- return;
-
- aNode.block();
- }
- - (void)visitAfterAllNode:(KWAfterAllNode *)aNode {
- if (aNode.block == nil)
- return;
-
- aNode.block();
- }
- - (void)visitBeforeEachNode:(KWBeforeEachNode *)aNode {
- if (aNode.block == nil)
- return;
-
- aNode.block();
- }
- - (void)visitAfterEachNode:(KWAfterEachNode *)aNode {
- if (aNode.block == nil)
- return;
-
- aNode.block();
- }
- - (void)visitLetNode:(KWLetNode *)aNode
- {
- [aNode evaluateTree];
- }
- - (void)visitItNode:(KWItNode *)aNode {
- if (aNode.block == nil || aNode != self.exampleNode)
- return;
-
- aNode.example = self;
-
- [aNode.context performExample:self withBlock:^{
-
- @try {
-
- aNode.block();
-
- #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
- NSException *invocationException = KWGetAndClearExceptionFromAcrossInvocationBoundary();
- [invocationException raise];
- #endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
-
- // Finish verifying and clear
- for (id<KWVerifying> verifier in self.verifiers) {
- [verifier exampleWillEnd];
- }
-
- if (self.unresolvedVerifier) {
- KWFailure *failure = [KWFailure failureWithCallSite:self.unresolvedVerifier.callSite format:@"expected subject not to be nil"];
- [self reportFailure:failure];
- }
-
- } @catch (NSException *exception) {
- KWFailure *failure = [KWFailure failureWithCallSite:aNode.callSite format:@"%@ \"%@\" raised",
- [exception name],
- [exception reason]];
- [self reportFailure:failure];
- }
-
- if (self.passed) {
- [self reportResultForExampleNodeWithLabel:@"PASSED"];
- }
-
- // Always clear stubs and spies at the end of it blocks
- KWClearStubsAndSpies();
- }];
- }
- - (void)visitPendingNode:(KWPendingNode *)aNode {
- if (aNode != self.exampleNode)
- return;
-
- [self reportResultForExampleNodeWithLabel:@"PENDING"];
- }
- - (NSString *)generateDescriptionForAnonymousItNode {
- // anonymous specify blocks should only have one verifier, but use the first in any case
- return [(self.verifiers)[0] descriptionForAnonymousItNode];
- }
- @end
- #pragma mark - Looking up CallSites
- KWCallSite *callSiteWithAddress(long address);
- KWCallSite *callSiteAtAddressIfNecessary(long address);
- KWCallSite *callSiteAtAddressIfNecessary(long address){
- BOOL shouldLookup = [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] isFocused] && ![[KWExampleSuiteBuilder sharedExampleSuiteBuilder] foundFocus];
- return shouldLookup ? [KWCallSite callSiteWithCallerAddress:address] : nil;
- }
- #pragma mark - Building Example Groups
- void describe(NSString *aDescription, void (^block)(void)) {
- KWCallSite *callSite = callSiteAtAddressIfNecessary(kwCallerAddress());
- describeWithCallSite(callSite, aDescription, block);
- }
- void context(NSString *aDescription, void (^block)(void)) {
- KWCallSite *callSite = callSiteAtAddressIfNecessary(kwCallerAddress());
- contextWithCallSite(callSite, aDescription, block);
- }
- void registerMatchers(NSString *aNamespacePrefix) {
- registerMatchersWithCallSite(nil, aNamespacePrefix);
- }
- void beforeAll(void (^block)(void)) {
- beforeAllWithCallSite(nil, block);
- }
- void afterAll(void (^block)(void)) {
- afterAllWithCallSite(nil, block);
- }
- void beforeEach(void (^block)(void)) {
- beforeEachWithCallSite(nil, block);
- }
- void afterEach(void (^block)(void)) {
- afterEachWithCallSite(nil, block);
- }
- void it(NSString *aDescription, void (^block)(void)) {
- KWCallSite *callSite = callSiteAtAddressIfNecessary(kwCallerAddress());
- itWithCallSite(callSite, aDescription, block);
- }
- void let_(__autoreleasing id *anObjectRef, const char *aSymbolName, id (^block)(void))
- {
- NSString *aDescription = [NSString stringWithUTF8String:aSymbolName];
- letWithCallSite(nil, anObjectRef, aDescription, block);
- }
- void specify(void (^block)(void))
- {
- itWithCallSite(nil, nil, block);
- }
- void pending_(NSString *aDescription, void (^ignoredBlock)(void)) {
- pendingWithCallSite(nil, aDescription, ignoredBlock);
- }
- void describeWithCallSite(KWCallSite *aCallSite, NSString *aDescription, void (^block)(void)) {
- contextWithCallSite(aCallSite, aDescription, block);
- }
- void contextWithCallSite(KWCallSite *aCallSite, NSString *aDescription, void (^block)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] pushContextNodeWithCallSite:aCallSite description:aDescription];
- block();
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] popContextNode];
- }
- void registerMatchersWithCallSite(KWCallSite *aCallSite, NSString *aNamespacePrefix) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] setRegisterMatchersNodeWithCallSite:aCallSite namespacePrefix:aNamespacePrefix];
- }
- void beforeAllWithCallSite(KWCallSite *aCallSite, void (^block)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] setBeforeAllNodeWithCallSite:aCallSite block:block];
- }
- void afterAllWithCallSite(KWCallSite *aCallSite, void (^block)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] setAfterAllNodeWithCallSite:aCallSite block:block];
- }
- void beforeEachWithCallSite(KWCallSite *aCallSite, void (^block)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] setBeforeEachNodeWithCallSite:aCallSite block:block];
- }
- void afterEachWithCallSite(KWCallSite *aCallSite, void (^block)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] setAfterEachNodeWithCallSite:aCallSite block:block];
- }
- void letWithCallSite(KWCallSite *aCallSite, __autoreleasing id *anObjectRef, NSString *aSymbolName, id (^block)(void))
- {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] addLetNodeWithCallSite:aCallSite objectRef:anObjectRef symbolName:aSymbolName block:block];
- }
- void itWithCallSite(KWCallSite *aCallSite, NSString *aDescription, void (^block)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] addItNodeWithCallSite:aCallSite description:aDescription block:block];
- }
- void pendingWithCallSite(KWCallSite *aCallSite, NSString *aDescription, void (^ignoredBlock)(void)) {
- [[KWExampleSuiteBuilder sharedExampleSuiteBuilder] addPendingNodeWithCallSite:aCallSite description:aDescription];
- }
|