Ver Fonte

Rewrite SRAutobahnTests to make them runtime discoverable and visible in Xcode.

Nikita Lutsenko há 9 anos atrás
pai
commit
396002c4a6

+ 10 - 4
SocketRocket.xcodeproj/project.pbxproj

@@ -18,7 +18,7 @@
 		3345DC881C52ACD70083CCB8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; };
 		3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		8105E4801CDD67B400AA12DB /* SRTAutobahnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E47A1CDD679A00AA12DB /* SRTAutobahnTests.m */; };
+		8105E4801CDD67B400AA12DB /* SRAutobahnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */; };
 		8105E4821CDD67BD00AA12DB /* SRTWebSocketOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */; };
 		8105E4841CDD67CE00AA12DB /* XCTestCase+SRTAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E47E1CDD679A00AA12DB /* XCTestCase+SRTAdditions.m */; };
 		8105E4AE1CDD6E6200AA12DB /* SRAutobahnOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */; };
@@ -34,6 +34,7 @@
 		8179958B1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
 		8179958C1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
 		8179958D1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
+		817996801CE184F40084DA37 /* SRAutobahnUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */; };
 		81B31C141CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C151CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C161CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
@@ -114,7 +115,7 @@
 		8105E4761CDD679A00AA12DB /* SRTWebSocketOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRTWebSocketOperation.h; sourceTree = "<group>"; };
 		8105E4771CDD679A00AA12DB /* SRTWebSocketOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRTWebSocketOperation.m; sourceTree = "<group>"; };
 		8105E4791CDD679A00AA12DB /* SRWebSocketTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SRWebSocketTests-Info.plist"; sourceTree = "<group>"; };
-		8105E47A1CDD679A00AA12DB /* SRTAutobahnTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRTAutobahnTests.m; sourceTree = "<group>"; };
+		8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnTests.m; sourceTree = "<group>"; };
 		8105E47D1CDD679A00AA12DB /* XCTestCase+SRTAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+SRTAdditions.h"; sourceTree = "<group>"; };
 		8105E47E1CDD679A00AA12DB /* XCTestCase+SRTAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCTestCase+SRTAdditions.m"; sourceTree = "<group>"; };
 		8105E4AC1CDD6E6200AA12DB /* SRAutobahnOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnOperation.h; sourceTree = "<group>"; };
@@ -123,6 +124,8 @@
 		811934B11CDAF711003AB243 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		817995841CE139700084DA37 /* SRDelegateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRDelegateController.h; sourceTree = "<group>"; };
 		817995851CE139700084DA37 /* SRDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDelegateController.m; sourceTree = "<group>"; };
+		8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnUtilities.h; sourceTree = "<group>"; };
+		8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnUtilities.m; sourceTree = "<group>"; };
 		81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumer.h; sourceTree = "<group>"; };
 		81B31C101CDC404100D86D43 /* SRIOConsumer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRIOConsumer.m; sourceTree = "<group>"; };
 		81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumerPool.h; sourceTree = "<group>"; };
@@ -235,7 +238,7 @@
 		8105E4741CDD679A00AA12DB /* Tests */ = {
 			isa = PBXGroup;
 			children = (
-				8105E47A1CDD679A00AA12DB /* SRTAutobahnTests.m */,
+				8105E47A1CDD679A00AA12DB /* SRAutobahnTests.m */,
 				8105E4751CDD679A00AA12DB /* Operations */,
 				8105E47C1CDD679A00AA12DB /* Utilities */,
 				8105E4781CDD679A00AA12DB /* Resources */,
@@ -266,6 +269,8 @@
 		8105E47C1CDD679A00AA12DB /* Utilities */ = {
 			isa = PBXGroup;
 			children = (
+				8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */,
+				8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */,
 				8105E47D1CDD679A00AA12DB /* XCTestCase+SRTAdditions.h */,
 				8105E47E1CDD679A00AA12DB /* XCTestCase+SRTAdditions.m */,
 			);
@@ -740,7 +745,8 @@
 			files = (
 				8105E4AE1CDD6E6200AA12DB /* SRAutobahnOperation.m in Sources */,
 				8105E4841CDD67CE00AA12DB /* XCTestCase+SRTAdditions.m in Sources */,
-				8105E4801CDD67B400AA12DB /* SRTAutobahnTests.m in Sources */,
+				817996801CE184F40084DA37 /* SRAutobahnUtilities.m in Sources */,
+				8105E4801CDD67B400AA12DB /* SRAutobahnTests.m in Sources */,
 				8105E4821CDD67BD00AA12DB /* SRTWebSocketOperation.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 146 - 0
Tests/SRAutobahnTests.m

@@ -0,0 +1,146 @@
+//
+// Copyright 2012 Square Inc.
+// Portions Copyright (c) 2016-present, Facebook, Inc.
+//
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+@import XCTest;
+@import ObjectiveC;
+
+#import <SocketRocket/SRWebSocket.h>
+
+#import "SRTWebSocketOperation.h"
+#import "SRAutobahnOperation.h"
+#import "XCTestCase+SRTAdditions.h"
+#import "SRAutobahnUtilities.h"
+
+@interface SRAutobahnTests : XCTestCase
+@end
+
+@implementation SRAutobahnTests
+
++ (NSArray<NSInvocation *> *)testInvocations
+{
+    __block NSArray<NSInvocation *> *array = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        NSMutableArray<NSInvocation *> *invocations = [NSMutableArray array];
+        for (NSUInteger i = 1; i <= SRAutobahnTestCaseCount(); i++) {
+            NSDictionary *caseInfo = SRAutobahnTestCaseInfo(i);
+            NSString *identifier = caseInfo[@"id"];
+
+            NSInvocation *invocation = [self invocationWithCaseNumber:i identifier:identifier];
+            [invocations addObject:invocation];
+        }
+
+        array = [invocations sortedArrayUsingComparator:^NSComparisonResult(NSInvocation *_Nonnull obj1, NSInvocation *_Nonnull obj2) {
+            return [NSStringFromSelector(obj1.selector) compare:NSStringFromSelector(obj2.selector) options:NSNumericSearch];
+        }];
+    });
+    return array;
+}
+
++ (void)updateReports
+{
+    SRAutobahnOperation *operation = SRAutobahnTestUpdateReportsOperation(SRAutobahnTestServerURL(), SRAutobahnTestAgentName());
+    [operation start];
+
+    SRRunLoopRunUntil(^BOOL{
+        return operation.isFinished;
+    }, 60 * 60);
+
+    NSAssert(!operation.error, @"Updating the report should not have errored %@", operation.error);
+}
+
+///--------------------------------------
+#pragma mark - Init
+///--------------------------------------
+
++ (instancetype)testCaseWithSelector:(SEL)selector
+{
+    NSArray<NSInvocation *> *invocations = [self testInvocations];
+    for (NSInvocation *invocation in invocations) {
+        if (invocation.selector == selector) {
+            return [super testCaseWithSelector:selector];
+        }
+    }
+    return nil;
+}
+
+///--------------------------------------
+#pragma mark - Setup
+///--------------------------------------
+
++ (NSInvocation *)invocationWithCaseNumber:(NSUInteger)caseNumber identifier:(NSString *)identifier
+{
+    SEL selector = [self addInstanceMethodForTestCaseNumber:caseNumber identifier:identifier];
+    NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector];
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+    invocation.selector = selector;
+    return invocation;
+}
+
++ (SEL)addInstanceMethodForTestCaseNumber:(NSInteger)caseNumber identifier:(NSString *)identifier
+{
+    NSString *selectorName = [NSString stringWithFormat:@"Case #%@", identifier];
+    SEL selector = NSSelectorFromString(selectorName);
+
+    IMP implementation = imp_implementationWithBlock(^(SRAutobahnTests *self) {
+        [self performTestWithCaseNumber:caseNumber identifier:identifier];
+    });
+    NSString *typeString = [NSString stringWithFormat:@"%s%s%s",  @encode(id), @encode(id), @encode(SEL)];
+    class_addMethod(self, selector, implementation, typeString.UTF8String);
+
+    return selector;
+}
+
+///--------------------------------------
+#pragma mark - Teardown
+///--------------------------------------
+
++ (void)tearDown
+{
+    [self updateReports];
+    [super tearDown];
+}
+
+///--------------------------------------
+#pragma mark - Test
+///--------------------------------------
+
+- (void)performTestWithCaseNumber:(NSInteger)caseNumber identifier:(NSString *)identifier
+{
+    NSURL *serverURL = SRAutobahnTestServerURL();
+    NSString *agent = SRAutobahnTestAgentName();
+
+    NSOperationQueue *testQueue = [[NSOperationQueue alloc] init];
+    testQueue.maxConcurrentOperationCount = 1;
+
+    SRAutobahnOperation *testOp = SRAutobahnTestOperation(serverURL, caseNumber, agent);
+    [testQueue addOperation:testOp];
+
+    __block NSDictionary *resultInfo = nil;
+    SRAutobahnOperation *resultOp = SRAutobahnTestResultOperation(serverURL, caseNumber, agent, ^(NSDictionary * _Nullable result) {
+        resultInfo = result;
+    });
+    [resultOp addDependency:testOp];
+    [testQueue addOperation:resultOp];
+
+    testQueue.suspended = NO;
+
+    [self runCurrentRunLoopUntilTestPasses:^BOOL{
+        return resultOp.isFinished;
+    } timeout:60 * 60];
+
+    XCTAssertTrue(!testOp.error, @"Test operation should not have failed");
+    if (!SRAutobahnIsValidResultBehavior(identifier, resultInfo[@"behavior"])) {
+        XCTFail(@"Invalid test behavior %@ for %@.", resultInfo[@"behavior"], identifier);
+    }
+}
+
+@end

+ 0 - 217
Tests/SRTAutobahnTests.m

@@ -1,217 +0,0 @@
-//
-// Copyright 2012 Square Inc.
-// Portions Copyright (c) 2016-present, Facebook, Inc.
-//
-// All rights reserved.
-//
-// This source code is licensed under the BSD-style license found in the
-// LICENSE file in the root directory of this source tree. An additional grant
-// of patent rights can be found in the PATENTS file in the same directory.
-//
-
-@import XCTest;
-
-#import <SocketRocket/SRWebSocket.h>
-
-#import "SRTWebSocketOperation.h"
-#import "SRAutobahnOperation.h"
-#import "XCTestCase+SRTAdditions.h"
-
-#define SRLogDebug(format, ...)
-//#define SRLogDebug(format, ...) NSLog(format, __VA_ARGS__)
-
-@interface SRTAutobahnTests : XCTestCase
-@end
-
-static NSDictionary<NSString *, id> *SRAutobahnTestConfiguration() {
-    static NSDictionary *configuration;
-    static dispatch_once_t onceToken;
-    dispatch_once(&onceToken, ^{
-        NSURL *configurationURL = [[NSBundle bundleForClass:[SRTAutobahnTests class]] URLForResource:@"autobahn_configuration"
-                                                                                       withExtension:@"json"];
-        NSInputStream *readStream = [NSInputStream inputStreamWithURL:configurationURL];
-        [readStream open];
-        configuration = [NSJSONSerialization JSONObjectWithStream:readStream options:0 error:nil];
-        [readStream close];
-    });
-    return configuration;
-}
-
-static BOOL SRAutobahnIsValidResultBehavior(NSString *caseIdentifier, NSString *behavior)
-{
-    if ([behavior isEqualToString:@"OK"]) {
-        return YES;
-    }
-
-    NSArray *cases = SRAutobahnTestConfiguration()[behavior];
-    for (NSString *caseId in cases) {
-        if ([caseIdentifier hasPrefix:caseId]) {
-            return YES;
-        }
-    }
-    return NO;
-}
-
-@implementation SRTAutobahnTests {
-    SRWebSocket *_curWebSocket;
-    NSInteger _testCount;
-    NSInteger _curTest;
-    NSMutableArray *_sockets;
-    NSString *_testURLString;
-    NSURL *_prefixURL;
-    NSString *_agent;
-    NSString *_description;
-    NSString *_identifier;
-}
-
-- (instancetype)initWithInvocation:(NSInvocation *)anInvocation description:(NSString *)description identifier:(NSString *)identifier
-{
-    self = [self initWithInvocation:anInvocation];
-    if (self) {
-        _description = description;
-        _identifier = identifier;
-    }
-    return self;
-}
-
-- (instancetype)initWithInvocation:(NSInvocation *)anInvocation;
-{
-    self = [super initWithInvocation:anInvocation];
-    if (self) {
-        _testURLString = [[NSProcessInfo processInfo].environment objectForKey:@"SR_TEST_URL"];
-        _prefixURL = [NSURL URLWithString:_testURLString];
-        _agent = [NSBundle bundleForClass:[self class]].bundleIdentifier;
-    }
-    return self;
-}
-
-- (NSUInteger)testCaseCount;
-{
-    if (self.invocation) {
-        return [super testCaseCount];
-    }
-
-    __block NSUInteger count = 0;
-    SRAutobahnOperation *caseGetter = SRAutobahnTestCaseCountOperation(_prefixURL, _agent, ^(NSInteger caseCount) {
-        count = caseCount;
-    });
-    [caseGetter start];
-
-    [self runCurrentRunLoopUntilTestPasses:^BOOL{
-        return caseGetter.isFinished;
-    } timeout:20.0];
-
-    XCTAssertNil(caseGetter.error, @"CaseGetter should have successfully returned the number of testCases. Instead got error %@", caseGetter.error);
-    return count;
-}
-
-- (void)performTest:(XCTestCaseRun *) aRun
-{
-    if (self.invocation) {
-        [super performTest:aRun];
-        return;
-    }
-    [aRun start];
-    for (NSUInteger i = 1; i <= aRun.test.testCaseCount; i++) {
-        SEL sel = @selector(performTestWithNumber:);
-        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:sel]];
-
-        invocation.selector = sel;
-        invocation.target = self;
-
-        [invocation setArgument:&i atIndex:2];
-        
-        NSDictionary *caseInfo = [self caseInfoForCaseNumber:i];
-        NSString *identifier = caseInfo[@"id"];
-        NSString *description = [NSString stringWithFormat:@"%@ - %@", caseInfo[@"id"], caseInfo[@"description"]];
-
-        XCTestCase *testCase = [[[self class] alloc] initWithInvocation:invocation description:description identifier:identifier];
-
-        XCTestCaseRun *run = [[XCTestCaseRun alloc] initWithTest:testCase];
-        [testCase performTest:run];
-    }
-    [aRun stop];
-
-    [self updateReports];
-}
-
-- (NSInteger)testNum;
-{
-    NSInteger i;
-    [self.invocation getArgument:&i atIndex:2];
-    return i;
-}
-
-- (NSDictionary *)caseInfoForCaseNumber:(NSInteger)caseNumber;
-{
-    __block NSDictionary *caseInfo = nil;
-    SRAutobahnOperation *testInfoOperation = SRAutobahnTestCaseInfoOperation(_prefixURL, caseNumber, ^(NSDictionary * _Nullable info) {
-        caseInfo = info;
-    });
-
-    [testInfoOperation start];
-
-    [self runCurrentRunLoopUntilTestPasses:^BOOL{
-        return testInfoOperation.isFinished;
-    } timeout:60 * 60];
-
-    XCTAssertNil(testInfoOperation.error, @"Updating the report should not have errored");
-    return caseInfo;
-}
-
-- (NSString *)description;
-{
-    if (_description) {
-        return _description;
-    } else {
-        return @"Autobahn Test Harness";
-    }
-}
-
-+ (id) defaultTestSuite
-{
-    return [[[self class] alloc] init];
-}
-
-- (void)performTestWithNumber:(NSInteger)testNumber;
-{
-    NSOperationQueue *testQueue = [[NSOperationQueue alloc] init];
-
-    testQueue.maxConcurrentOperationCount = 1;
-
-    SRAutobahnOperation *testOp = SRAutobahnTestOperation(_prefixURL, testNumber, _agent);
-    [testQueue addOperation:testOp];
-
-    __block NSDictionary *resultInfo = nil;
-
-    SRAutobahnOperation *resultOp = SRAutobahnTestResultOperation(_prefixURL, testNumber, _agent, ^(NSDictionary * _Nullable result) {
-        resultInfo = result;
-    });
-    [resultOp addDependency:testOp];
-    [testQueue addOperation:resultOp];
-
-    testQueue.suspended = NO;
-
-    [self runCurrentRunLoopUntilTestPasses:^BOOL{
-        return resultOp.isFinished;
-    } timeout:60 * 60];
-
-    XCTAssertTrue(!testOp.error, @"Test operation should not have failed");
-    if (!SRAutobahnIsValidResultBehavior(_identifier, resultInfo[@"behavior"])) {
-        XCTFail(@"Invalid test behavior %@ for %@.", resultInfo[@"behavior"], _identifier);
-    }
-}
-
-- (void)updateReports
-{
-    SRAutobahnOperation *operation = SRAutobahnTestUpdateReportsOperation(_prefixURL, _agent);
-    [operation start];
-
-    [self runCurrentRunLoopUntilTestPasses:^BOOL{
-        return operation.isFinished;
-    } timeout:60 * 60];
-
-    XCTAssertNil(operation.error, @"Updating the report should not have errored");
-}
-
-@end

+ 41 - 0
Tests/Utilities/SRAutobahnUtilities.h

@@ -0,0 +1,41 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+@import Foundation;
+
+NS_ASSUME_NONNULL_BEGIN
+
+///--------------------------------------
+#pragma mark - Test Configuration
+///--------------------------------------
+
+extern NSString *SRAutobahnTestAgentName(void);
+extern NSURL *SRAutobahnTestServerURL(void);
+
+///--------------------------------------
+#pragma mark - Validation
+///--------------------------------------
+
+extern NSDictionary<NSString *, id> *SRAutobahnTestConfiguration(void);
+extern BOOL SRAutobahnIsValidResultBehavior(NSString *caseIdentifier, NSString *behavior);
+
+///--------------------------------------
+#pragma mark - Utilities
+///--------------------------------------
+
+extern BOOL SRRunLoopRunUntil(BOOL (^predicate)(), NSTimeInterval timeout);
+
+///--------------------------------------
+#pragma mark - Setup
+///--------------------------------------
+
+extern NSUInteger SRAutobahnTestCaseCount(void);
+extern NSDictionary<NSString *, id> *SRAutobahnTestCaseInfo(NSInteger caseNumber);
+
+NS_ASSUME_NONNULL_END

+ 128 - 0
Tests/Utilities/SRAutobahnUtilities.m

@@ -0,0 +1,128 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import "SRAutobahnUtilities.h"
+
+#import "SRAutobahnOperation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SRAutobahnUtilities : NSObject @end
+@implementation SRAutobahnUtilities @end
+
+///--------------------------------------
+#pragma mark - Test Configuration
+///--------------------------------------
+
+NSString *SRAutobahnTestAgentName(void)
+{
+    return [NSBundle bundleForClass:[SRAutobahnUtilities class]].bundleIdentifier;
+}
+
+NSURL *SRAutobahnTestServerURL(void)
+{
+    NSString *serverURLString = [[NSProcessInfo processInfo].environment objectForKey:@"SR_TEST_URL"];
+    return [NSURL URLWithString:serverURLString];
+}
+
+///--------------------------------------
+#pragma mark - Validation
+///--------------------------------------
+
+NSDictionary<NSString *, id> *SRAutobahnTestConfiguration(void)
+{
+    static NSDictionary *configuration;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        NSURL *configurationURL = [[NSBundle bundleForClass:[SRAutobahnUtilities class]] URLForResource:@"autobahn_configuration"
+                                                                                          withExtension:@"json"];
+        NSInputStream *readStream = [NSInputStream inputStreamWithURL:configurationURL];
+        [readStream open];
+        configuration = [NSJSONSerialization JSONObjectWithStream:readStream options:0 error:nil];
+        [readStream close];
+    });
+    return configuration;
+}
+
+BOOL SRAutobahnIsValidResultBehavior(NSString *caseIdentifier, NSString *behavior)
+{
+    if ([behavior isEqualToString:@"OK"]) {
+        return YES;
+    }
+
+    NSArray *cases = SRAutobahnTestConfiguration()[behavior];
+    for (NSString *caseId in cases) {
+        if ([caseIdentifier hasPrefix:caseId]) {
+            return YES;
+        }
+    }
+    return NO;
+}
+
+///--------------------------------------
+#pragma mark - Utilities
+///--------------------------------------
+
+BOOL SRRunLoopRunUntil(BOOL (^predicate)(), NSTimeInterval timeout)
+{
+    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
+
+    NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate];
+    NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
+
+    while (!predicate() && currentTime < timeoutTime) {
+        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+        currentTime = [NSDate timeIntervalSinceReferenceDate];
+    }
+    return (currentTime <= timeoutTime);
+}
+
+///--------------------------------------
+#pragma mark - Setup
+///--------------------------------------
+
+NSUInteger SRAutobahnTestCaseCount(void)
+{
+    static NSUInteger count;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        SRAutobahnOperation *caseGetter = SRAutobahnTestCaseCountOperation(SRAutobahnTestServerURL(),
+                                                                           SRAutobahnTestAgentName(),
+                                                                           ^(NSInteger caseCount) {
+                                                                               count = caseCount;
+                                                                           });
+        [caseGetter start];
+
+        SRRunLoopRunUntil(^BOOL{
+            return caseGetter.isFinished;
+        }, 20.0);
+
+        NSCAssert(!caseGetter.error, @"CaseGetter should have successfully returned the number of testCases. Instead got error %@", caseGetter.error);
+    });
+    return count;
+}
+
+NSDictionary<NSString *, id> *SRAutobahnTestCaseInfo(NSInteger caseNumber)
+{
+    __block NSDictionary *caseInfo = nil;
+    SRAutobahnOperation *testInfoOperation = SRAutobahnTestCaseInfoOperation(SRAutobahnTestServerURL(), caseNumber, ^(NSDictionary * _Nullable info) {
+        caseInfo = info;
+    });
+
+    [testInfoOperation start];
+
+    SRRunLoopRunUntil(^BOOL{
+        return testInfoOperation.isFinished;
+    }, 60 * 60);
+
+    NSCAssert(!testInfoOperation.error, @"Updating the report should not have errored");
+    return caseInfo;
+}
+
+NS_ASSUME_NONNULL_END

+ 3 - 10
Tests/Utilities/XCTestCase+SRTAdditions.m

@@ -11,20 +11,13 @@
 
 #import "XCTestCase+SRTAdditions.h"
 
+#import "SRAutobahnUtilities.h"
+
 @implementation XCTestCase (SRTAdditions)
 
 - (void)runCurrentRunLoopUntilTestPasses:(BOOL (^)())predicate timeout:(NSTimeInterval)timeout
 {
-    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
-
-    NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate];
-    NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
-
-    while (!predicate() && currentTime < timeoutTime) {
-        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
-        currentTime = [NSDate timeIntervalSinceReferenceDate];
-    }
-    XCTAssertTrue(currentTime <= timeoutTime, @"Timed out");
+    XCTAssertTrue(SRRunLoopRunUntil(predicate, timeout), @"Timed out");
 }
 
 @end