Run Xcode tests in source file order

Turns out, the layout of functions in object files generated by Clang matches their order in the source file. Simply sorting test method invocations by addresses of their implementations changes the default alphabetical order to the order they appear in the source code.

The obvious downside is that there is absolutely no guarantee the next clang version will not break it. The layout of object files is entirely up to compiler, and theoretically change for optimization reasons.

To use this trick, add this file to your test project:

//  XCTestCase+HGRunTestsInSourceOrder.m
//  http://hamstergene.github.io/post/xcode-run-xctest-in-source-order/
//  Created by Evgeny Khomyakov on 2016-03-31.

#import <XCTest/XCTest.h>
#import <objc/runtime.h>

@interface XCTestCase (HGRunTestsInSourceOrder)

@end

@implementation XCTestCase (HGRunTestsInSourceOrder)

+ (NSArray*) testInvocations_swizzle_HGRunTestsInSourceOrder;
{
    NSMutableArray* invocations = [[self testInvocations_swizzle_HGRunTestsInSourceOrder] mutableCopy];
    [invocations sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        Method m1 = class_getInstanceMethod(self, [obj1 selector]);
        Method m2 = class_getInstanceMethod(self, [obj2 selector]);

        void* imp1 = method_getImplementation(m1);
        void* imp2 = method_getImplementation(m2);

        if (imp1 == imp2)
            return NSOrderedSame;
        return (imp1 < imp2 ? NSOrderedAscending : NSOrderedDescending);
    }];
    return invocations;
}

+ (void) load;
{
    Method originalMethod = class_getClassMethod(XCTestCase.class, @selector(testInvocations));
    Method replacementMethod = class_getClassMethod(XCTestCase.class, @selector(testInvocations_swizzle_HGRunTestsInSourceOrder));
    NSAssert(originalMethod && replacementMethod, @"Bad luck: XCTestCase implementation has changed, this category no longer works.");

    method_exchangeImplementations(originalMethod, replacementMethod);
}

@end
comments powered by Disqus