Skip to content

Commit

Permalink
Swizzle XCTestObservationCenter observer registration methods (#278)
Browse files Browse the repository at this point in the history
Swizzle XCTestObservationCenter observer registration methods
  • Loading branch information
phatblat committed Apr 7, 2016
2 parents ba7a9f7 + 54abe2f commit 1a9e53e
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 35 deletions.
22 changes: 12 additions & 10 deletions Nimble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
1F0648D41963AAB2001F9C46 /* SynchronousTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648D31963AAB2001F9C46 /* SynchronousTests.swift */; };
1F0648D51963AAB2001F9C46 /* SynchronousTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648D31963AAB2001F9C46 /* SynchronousTests.swift */; };
1F14FB64194180C5009F2A08 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F14FB63194180C5009F2A08 /* utils.swift */; };
1F1871C41CA89EDB00A34BF2 /* CurrentTestCaseTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BB1CA89EDB00A34BF2 /* CurrentTestCaseTracker.m */; };
1F1871C51CA89EDB00A34BF2 /* DSL.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F1871BC1CA89EDB00A34BF2 /* DSL.h */; settings = {ATTRIBUTES = (Public, ); }; };
1F1871C61CA89EDB00A34BF2 /* DSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BD1CA89EDB00A34BF2 /* DSL.m */; };
1F1871C71CA89EDB00A34BF2 /* NMBExceptionCapture.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F1871BE1CA89EDB00A34BF2 /* NMBExceptionCapture.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand All @@ -21,11 +20,9 @@
1F1871CA1CA89EDB00A34BF2 /* NMBStringify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871C11CA89EDB00A34BF2 /* NMBStringify.m */; };
1F1871CB1CA89EDB00A34BF2 /* NMBExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871C21CA89EDB00A34BF2 /* NMBExpectation.swift */; };
1F1871CC1CA89EDB00A34BF2 /* NMBObjCMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871C31CA89EDB00A34BF2 /* NMBObjCMatcher.swift */; };
1F1871D11CA89EEE00A34BF2 /* CurrentTestCaseTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BB1CA89EDB00A34BF2 /* CurrentTestCaseTracker.m */; };
1F1871D21CA89EEE00A34BF2 /* DSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BD1CA89EDB00A34BF2 /* DSL.m */; };
1F1871D31CA89EEE00A34BF2 /* NMBExceptionCapture.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BF1CA89EDB00A34BF2 /* NMBExceptionCapture.m */; };
1F1871D41CA89EEE00A34BF2 /* NMBStringify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871C11CA89EDB00A34BF2 /* NMBStringify.m */; };
1F1871D51CA89EEF00A34BF2 /* CurrentTestCaseTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BB1CA89EDB00A34BF2 /* CurrentTestCaseTracker.m */; };
1F1871D61CA89EEF00A34BF2 /* DSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BD1CA89EDB00A34BF2 /* DSL.m */; };
1F1871D71CA89EEF00A34BF2 /* NMBExceptionCapture.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871BF1CA89EDB00A34BF2 /* NMBExceptionCapture.m */; };
1F1871D81CA89EEF00A34BF2 /* NMBStringify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F1871C11CA89EDB00A34BF2 /* NMBStringify.m */; };
Expand Down Expand Up @@ -314,6 +311,9 @@
DDB4D5F119FE442800E9D9FE /* MatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB4D5EF19FE442800E9D9FE /* MatchTest.swift */; };
DDEFAEB41A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */; };
DDEFAEB51A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */; };
F8A1BE2F1CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A1BE2B1CB3710900031679 /* XCTestObservationCenter+Register.m */; };
F8A1BE301CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A1BE2B1CB3710900031679 /* XCTestObservationCenter+Register.m */; };
F8A1BE311CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A1BE2B1CB3710900031679 /* XCTestObservationCenter+Register.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -386,7 +386,6 @@
1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectWithLazyProperty.swift; sourceTree = "<group>"; };
1F0648D31963AAB2001F9C46 /* SynchronousTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousTests.swift; sourceTree = "<group>"; };
1F14FB63194180C5009F2A08 /* utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = utils.swift; sourceTree = "<group>"; };
1F1871BB1CA89EDB00A34BF2 /* CurrentTestCaseTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CurrentTestCaseTracker.m; sourceTree = "<group>"; };
1F1871BC1CA89EDB00A34BF2 /* DSL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DSL.h; sourceTree = "<group>"; };
1F1871BD1CA89EDB00A34BF2 /* DSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DSL.m; sourceTree = "<group>"; };
1F1871BE1CA89EDB00A34BF2 /* NMBExceptionCapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NMBExceptionCapture.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -504,6 +503,8 @@
DDB4D5EC19FE43C200E9D9FE /* Match.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Match.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
DDB4D5EF19FE442800E9D9FE /* MatchTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatchTest.swift; sourceTree = "<group>"; };
DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCAllPassTest.m; sourceTree = "<group>"; };
F8A1BE2B1CB3710900031679 /* XCTestObservationCenter+Register.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCTestObservationCenter+Register.m"; sourceTree = "<group>"; };
F8A1BE321CB3777F00031679 /* CurrentTestCaseTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CurrentTestCaseTracker.h; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -568,15 +569,16 @@
1F1871B91CA89E1B00A34BF2 /* ObjectiveC */ = {
isa = PBXGroup;
children = (
1F1871BB1CA89EDB00A34BF2 /* CurrentTestCaseTracker.m */,
F8A1BE321CB3777F00031679 /* CurrentTestCaseTracker.h */,
1F1871BC1CA89EDB00A34BF2 /* DSL.h */,
1F1871BD1CA89EDB00A34BF2 /* DSL.m */,
1F1871BE1CA89EDB00A34BF2 /* NMBExceptionCapture.h */,
1F1871BF1CA89EDB00A34BF2 /* NMBExceptionCapture.m */,
1F1871C01CA89EDB00A34BF2 /* NMBStringify.h */,
1F1871C11CA89EDB00A34BF2 /* NMBStringify.m */,
1F1871C21CA89EDB00A34BF2 /* NMBExpectation.swift */,
1F1871C31CA89EDB00A34BF2 /* NMBObjCMatcher.swift */,
1F1871C01CA89EDB00A34BF2 /* NMBStringify.h */,
1F1871C11CA89EDB00A34BF2 /* NMBStringify.m */,
F8A1BE2B1CB3710900031679 /* XCTestObservationCenter+Register.m */,
);
path = ObjectiveC;
sourceTree = "<group>";
Expand Down Expand Up @@ -1048,7 +1050,6 @@
1F43728A1A1B343800EB80F8 /* Functional.swift in Sources */,
AE4BA9AD1C88DDB500B73906 /* Errors.swift in Sources */,
1FD8CD3C1968AB07008ED995 /* BeAnInstanceOf.swift in Sources */,
1F1871C41CA89EDB00A34BF2 /* CurrentTestCaseTracker.m in Sources */,
1FD8CD501968AB07008ED995 /* BeLogical.swift in Sources */,
1F1871CB1CA89EDB00A34BF2 /* NMBExpectation.swift in Sources */,
DA9E8C821A414BB9002633C2 /* DSL+Wait.swift in Sources */,
Expand All @@ -1063,6 +1064,7 @@
1FD8CD4C1968AB07008ED995 /* BeLessThan.swift in Sources */,
1F1871CC1CA89EDB00A34BF2 /* NMBObjCMatcher.swift in Sources */,
1FD8CD461968AB07008ED995 /* BeGreaterThan.swift in Sources */,
F8A1BE2F1CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */,
1F1871C61CA89EDB00A34BF2 /* DSL.m in Sources */,
1FD8CD301968AB07008ED995 /* AdapterProtocols.swift in Sources */,
AE7ADE451C80BF8000B94CD3 /* MatchError.swift in Sources */,
Expand Down Expand Up @@ -1170,7 +1172,6 @@
1F5DF1761BDCA0F500C3A531 /* AllPass.swift in Sources */,
AE4BA9AF1C88DDB500B73906 /* Errors.swift in Sources */,
1F5DF1861BDCA0F500C3A531 /* HaveCount.swift in Sources */,
1F1871D51CA89EEF00A34BF2 /* CurrentTestCaseTracker.m in Sources */,
1F5DF1811BDCA0F500C3A531 /* BeLogical.swift in Sources */,
1F1871DB1CA89EF100A34BF2 /* NMBExpectation.swift in Sources */,
1F5DF1741BDCA0F500C3A531 /* Expression.swift in Sources */,
Expand All @@ -1183,6 +1184,7 @@
1F5DF1831BDCA0F500C3A531 /* Contain.swift in Sources */,
1F5DF1851BDCA0F500C3A531 /* Equal.swift in Sources */,
1F1871DC1CA89EF100A34BF2 /* NMBObjCMatcher.swift in Sources */,
F8A1BE311CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */,
1F5DF1711BDCA0F500C3A531 /* DSL+Wait.swift in Sources */,
1F1871D61CA89EEF00A34BF2 /* DSL.m in Sources */,
1F5DF17D1BDCA0F500C3A531 /* BeGreaterThanOrEqualTo.swift in Sources */,
Expand Down Expand Up @@ -1263,7 +1265,6 @@
1F43728B1A1B343900EB80F8 /* Functional.swift in Sources */,
AE4BA9AE1C88DDB500B73906 /* Errors.swift in Sources */,
1FD8CD3D1968AB07008ED995 /* BeAnInstanceOf.swift in Sources */,
1F1871D11CA89EEE00A34BF2 /* CurrentTestCaseTracker.m in Sources */,
1FD8CD511968AB07008ED995 /* BeLogical.swift in Sources */,
1F1871D91CA89EF100A34BF2 /* NMBExpectation.swift in Sources */,
DA9E8C831A414BB9002633C2 /* DSL+Wait.swift in Sources */,
Expand All @@ -1277,6 +1278,7 @@
1FD8CD5B1968AB07008ED995 /* Equal.swift in Sources */,
1FD8CD4D1968AB07008ED995 /* BeLessThan.swift in Sources */,
1FD8CD471968AB07008ED995 /* BeGreaterThan.swift in Sources */,
F8A1BE301CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */,
1F1871DA1CA89EF100A34BF2 /* NMBObjCMatcher.swift in Sources */,
1FD8CD311968AB07008ED995 /* AdapterProtocols.swift in Sources */,
1F1871D21CA89EEE00A34BF2 /* DSL.m in Sources */,
Expand Down
9 changes: 9 additions & 0 deletions Sources/Nimble/Adapters/ObjectiveC/CurrentTestCaseTracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import <XCTest/XCTest.h>
#import <Nimble/Nimble-Swift.h>

SWIFT_CLASS("_TtC6Nimble22CurrentTestCaseTracker")
@interface CurrentTestCaseTracker : NSObject <XCTestObservation>
+ (CurrentTestCaseTracker *)sharedInstance;
@end

@interface CurrentTestCaseTracker (Register) @end
25 changes: 0 additions & 25 deletions Sources/Nimble/Adapters/ObjectiveC/CurrentTestCaseTracker.m

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#import "CurrentTestCaseTracker.h"
#import <XCTest/XCTest.h>
#import <objc/runtime.h>

#pragma mark - Method Swizzling

/// Swaps the implementations between two instance methods.
///
/// @param class The class containing `originalSelector`.
/// @param originalSelector Original method to replace.
/// @param replacementSelector Replacement method.
void swizzleSelectors(Class class, SEL originalSelector, SEL replacementSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method replacementMethod = class_getInstanceMethod(class, replacementSelector);

BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(replacementMethod),
method_getTypeEncoding(replacementMethod));

if (didAddMethod) {
class_replaceMethod(class,
replacementSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, replacementMethod);
}
}

#pragma mark - Private

@interface XCTestObservationCenter (Private)
- (void)_addLegacyTestObserver:(id)observer;
@end

@implementation XCTestObservationCenter (Register)

/// Uses objc method swizzling to register `CurrentTestCaseTracker` as a test observer. This is necessary
/// because Xcode 7.3 introduced timing issues where if a custom `XCTestObservation` is registered too early
/// it suppresses all console output (generated by `XCTestLog`), breaking any tools that depend on this output.
/// This approach waits to register our custom test observer until XCTest adds its first "legacy" observer,
/// falling back to registering after the first normal observer if this private method ever changes.
+ (void)load {
if (class_getInstanceMethod([self class], @selector(_addLegacyTestObserver:))) {
// Swizzle -_addLegacyTestObserver:
swizzleSelectors([self class], @selector(_addLegacyTestObserver:), @selector(NMB_original__addLegacyTestObserver:));
} else {
// Swizzle -addTestObserver:, only if -_addLegacyTestObserver: is not implemented
swizzleSelectors([self class], @selector(addTestObserver:), @selector(NMB_original_addTestObserver:));
}
}

#pragma mark - Replacement Methods

/// Registers `CurrentTestCaseTracker` as a test observer after `XCTestLog` has been added.
- (void)NMB_original__addLegacyTestObserver:(id)observer {
[self NMB_original__addLegacyTestObserver:observer];

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self addTestObserver:[CurrentTestCaseTracker sharedInstance]];
});
}

/// Registers `CurrentTestCaseTracker` as a test observer after `XCTestLog` has been added.
/// This method is only used if `-_addLegacyTestObserver:` is not impelemented. (added in Xcode 7.3)
- (void)NMB_original_addTestObserver:(id<XCTestObservation>)observer {
[self NMB_original_addTestObserver:observer];

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self NMB_original_addTestObserver:[CurrentTestCaseTracker sharedInstance]];
});
}

@end

0 comments on commit 1a9e53e

Please sign in to comment.