Permalink
Browse files

Begun implementation of AQAppStateMachine tests.

  • Loading branch information...
1 parent 82e973f commit cf4417a574f0a84542277bc2306757fd6e77c837 @AlanQuatermain committed Jun 22, 2011
View
16 AQAppStateMachine.xcodeproj/project.pbxproj
@@ -93,6 +93,10 @@
38AC30BC13AA3D5C00AB071C /* AQBitfieldPredicates.h in Headers */ = {isa = PBXBuildFile; fileRef = 38AC30BA13AA3D5C00AB071C /* AQBitfieldPredicates.h */; };
38AC30BD13AA3D5C00AB071C /* AQBitfieldPredicates.m in Sources */ = {isa = PBXBuildFile; fileRef = 38AC30BB13AA3D5C00AB071C /* AQBitfieldPredicates.m */; };
38AC30BE13AA3D5C00AB071C /* AQBitfieldPredicates.m in Sources */ = {isa = PBXBuildFile; fileRef = 38AC30BB13AA3D5C00AB071C /* AQBitfieldPredicates.m */; };
+ 38F0AA1513B2638B006E014F /* AQStateMatchingDescriptorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38F0AA1413B2638B006E014F /* AQStateMatchingDescriptorTests.m */; };
+ 38F0AA1913B268F1006E014F /* AQRangeMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 38F0AA1713B268F1006E014F /* AQRangeMethods.h */; };
+ 38F0AA1A13B268F1006E014F /* AQRangeMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = 38F0AA1813B268F1006E014F /* AQRangeMethods.m */; };
+ 38F0AA1B13B268F1006E014F /* AQRangeMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = 38F0AA1813B268F1006E014F /* AQRangeMethods.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -185,6 +189,10 @@
3866945D13AF8AF300268560 /* AQAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQAdditions.m; sourceTree = "<group>"; };
38AC30BA13AA3D5C00AB071C /* AQBitfieldPredicates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQBitfieldPredicates.h; sourceTree = "<group>"; };
38AC30BB13AA3D5C00AB071C /* AQBitfieldPredicates.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQBitfieldPredicates.m; sourceTree = "<group>"; };
+ 38F0AA1313B2638B006E014F /* AQStateMatchingDescriptorTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQStateMatchingDescriptorTests.h; sourceTree = "<group>"; };
+ 38F0AA1413B2638B006E014F /* AQStateMatchingDescriptorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQStateMatchingDescriptorTests.m; sourceTree = "<group>"; };
+ 38F0AA1713B268F1006E014F /* AQRangeMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQRangeMethods.h; sourceTree = "<group>"; };
+ 38F0AA1813B268F1006E014F /* AQRangeMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQRangeMethods.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -255,6 +263,8 @@
3866939413AAA0C600268560 /* AQAppStateMachine.m */,
3866939813ABC70200268560 /* AQStateMatchingDescriptor.h */,
3866939913ABC70300268560 /* AQStateMatchingDescriptor.m */,
+ 38F0AA1713B268F1006E014F /* AQRangeMethods.h */,
+ 38F0AA1813B268F1006E014F /* AQRangeMethods.m */,
3866940A13AF8A1F00268560 /* SortedDictionary */,
38431B5A13A7C26800178A7E /* Supporting Files */,
);
@@ -282,6 +292,8 @@
3821C4A313B23C8500175CEE /* AQRangeTests.m */,
3821C4A513B240A900175CEE /* AQNotifyingBitfieldTests.h */,
3821C4A613B240A900175CEE /* AQNotifyingBitfieldTests.m */,
+ 38F0AA1313B2638B006E014F /* AQStateMatchingDescriptorTests.h */,
+ 38F0AA1413B2638B006E014F /* AQStateMatchingDescriptorTests.m */,
386693EF13AF8A1500268560 /* SortedDictionary */,
38431B6D13A7C26900178A7E /* Supporting Files */,
);
@@ -421,6 +433,7 @@
3866945513AF8A1F00268560 /* MutableSortedDictionary.h in Headers */,
3866945813AF8A1F00268560 /* SortedDictionary.h in Headers */,
3866945B13AF8A1F00268560 /* SortedDictionaryEntry.h in Headers */,
+ 38F0AA1913B268F1006E014F /* AQRangeMethods.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -545,6 +558,7 @@
3866945613AF8A1F00268560 /* MutableSortedDictionary.m in Sources */,
3866945913AF8A1F00268560 /* SortedDictionary.m in Sources */,
3866945F13AF8AF300268560 /* AQAdditions.m in Sources */,
+ 38F0AA1A13B268F1006E014F /* AQRangeMethods.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -585,6 +599,8 @@
3821C4A113B2322400175CEE /* AQBitfieldPredicateTests.m in Sources */,
3821C4A413B23C8500175CEE /* AQRangeTests.m in Sources */,
3821C4A713B240A900175CEE /* AQNotifyingBitfieldTests.m in Sources */,
+ 38F0AA1513B2638B006E014F /* AQStateMatchingDescriptorTests.m in Sources */,
+ 38F0AA1B13B268F1006E014F /* AQRangeMethods.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
28 AQAppStateMachine/AQRangeMethods.h
@@ -0,0 +1,28 @@
+//
+// AQRangeMethods.h
+// AQAppStateMachine
+//
+// Created by Jim Dovey on 11-06-22.
+// Copyright 2011 Jim Dovey. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSIndexSet (AQRangeMethods)
+
+// so the compiler is happy if we don't target iOS 5 at all
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_5_0
+- (void)enumerateRangesUsingBlock:(void (^)(NSRange range, BOOL *stop))block;
+- (void)enumerateRangesWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(NSRange range, BOOL *stop))block;
+- (void)enumerateRangesInRange:(NSRange)range options:(NSEnumerationOptions)opts usingBlock:(void (^)(NSRange range, BOOL *stop))block;
+#endif
+
+// our implementations, to be swapped in if necessary at runtime
+- (void)aq_enumerateRangesUsingBlock:(void (^)(NSRange range, BOOL *stop))block;
+- (void)aq_enumerateRangesWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(NSRange range, BOOL *stop))block;
+- (void)aq_enumerateRangesInRange:(NSRange)range options:(NSEnumerationOptions)opts usingBlock:(void (^)(NSRange range, BOOL *stop))block;
+
+// custom methods
+- (NSUInteger) numberOfRanges;
+
+@end
View
102 AQAppStateMachine/AQRangeMethods.m
@@ -0,0 +1,102 @@
+//
+// AQRangeMethods.m
+// AQAppStateMachine
+//
+// Created by Jim Dovey on 11-06-22.
+// Copyright 2011 Jim Dovey. All rights reserved.
+//
+
+#import "AQRangeMethods.h"
+#import <objc/runtime.h>
+#import <objc/message.h>
+
+static void DuplicateMethod(Class cls, SEL from, SEL to)
+{
+ Method fromMethod = class_getInstanceMethod(cls, from);
+ const char * types = method_getTypeEncoding(fromMethod);
+ class_addMethod(cls, to, method_getImplementation(fromMethod), types);
+}
+
+@implementation NSIndexSet (AQRangeMethods)
+
++ (void) load
+{
+ if ( [self instancesRespondToSelector: @selector(enumerateRangesUsingBlock:)] )
+ return;
+
+ DuplicateMethod(self, @selector(aq_enumerateRangesUsingBlock:), @selector(enumerateRangesUsingBlock:));
+ DuplicateMethod(self, @selector(aq_enumerateRangesWithOptions:usingBlock:), @selector(enumerateRangesWithOptions:usingBlock:));
+ DuplicateMethod(self, @selector(aq_enumerateRangesInRange:options:usingBlock:), @selector(enumerateRangesInRange:options:usingBlock:));
+}
+
+- (void)aq_enumerateRangesUsingBlock:(void (^)(NSRange range, BOOL *stop))block
+{
+ [self enumerateRangesInRange: NSMakeRange(0, NSNotFound) options: 0 usingBlock: block];
+}
+
+- (void)aq_enumerateRangesWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(NSRange range, BOOL *stop))block
+{
+ [self enumerateRangesInRange: NSMakeRange(0, NSNotFound) options: opts usingBlock: block];
+}
+
+- (void)aq_enumerateRangesInRange:(NSRange)range options:(NSEnumerationOptions)opts usingBlock:(void (^)(NSRange range, BOOL *stop))block
+{
+ __block NSUInteger rangeStartIndex = NSNotFound;
+ __block NSUInteger currentIndex = NSNotFound;
+
+ dispatch_group_t group = NULL;
+ if ( (opts & NSEnumerationConcurrent) == NSEnumerationConcurrent)
+ group = dispatch_group_create();
+
+ BOOL (^isContiguous)(NSUInteger) = ^BOOL(NSUInteger idx) { return ( idx == currentIndex+1 ); };
+ if ( (opts & NSEnumerationReverse) == NSEnumerationReverse )
+ {
+ isContiguous = ^BOOL(NSUInteger idx) { return ( idx == currentIndex-1 ); };
+ }
+
+ [self enumerateIndexesInRange: range options: opts usingBlock: ^(NSUInteger idx, BOOL *stop) {
+ if ( currentIndex == NSNotFound )
+ {
+ rangeStartIndex = idx;
+ currentIndex = idx;
+ return;
+ }
+
+ if ( isContiguous(idx) )
+ return;
+
+ // create a range
+ NSRange range;
+ if ( (opts & NSEnumerationReverse) == NSEnumerationReverse )
+ {
+ range.location = currentIndex;
+ range.length = (rangeStartIndex - currentIndex) + 1;
+ }
+ else
+ {
+ range.location = rangeStartIndex;
+ range.length = (currentIndex - rangeStartIndex) + 1;
+ }
+
+ if ( (opts & NSEnumerationConcurrent) == NSEnumerationConcurrent )
+ {
+ dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ block(range, stop); });
+ }
+ else
+ {
+ block(range, stop);
+ }
+
+ currentIndex = idx;
+ rangeStartIndex = idx;
+ }];
+}
+
+- (NSUInteger) numberOfRanges
+{
+ __block NSUInteger result = 0;
+ [self enumerateRangesUsingBlock: ^(NSRange range, BOOL *stop) { result += 1; }];
+ return ( result );
+}
+
+@end
View
2 AQAppStateMachine/AQStateMatchingDescriptor.h
@@ -18,7 +18,7 @@
// designated initializer
// to specify 'no mask' in an array, use NSNull
-- (id) initWithRanges: (NSIndexSet *) ranges matchingMasks: (NSArray *) masks;
+- (id) initWithRanges: (NSArray *) ranges matchingMasks: (NSArray *) masks;
@property (nonatomic, readonly) NSString * uniqueID;
@property (nonatomic, readonly) NSRange fullRange;
View
30 AQAppStateMachine/AQStateMatchingDescriptor.m
@@ -9,12 +9,13 @@
#import "AQStateMatchingDescriptor.h"
#import "AQRange.h"
#import "AQBitfield.h"
+#import "AQRangeMethods.h"
@implementation AQStateMatchingDescriptor
@synthesize uniqueID=_uuid;
-- (id) initWithRanges: (NSIndexSet *) ranges matchingMasks: (NSArray *) masks
+- (id) initWithRanges: (NSArray *) ranges matchingMasks: (NSArray *) masks
{
NSParameterAssert([masks count] == 0 || [ranges count] == [masks count]);
@@ -28,12 +29,12 @@ - (id) initWithRanges: (NSIndexSet *) ranges matchingMasks: (NSArray *) masks
CFRelease(uuid);
NSMutableIndexSet * indices = [NSMutableIndexSet new];
- __block NSUInteger idx = 0;
- [ranges enumerateRangesUsingBlock: ^(NSRange range, BOOL *stop) {
+ [ranges enumerateObjectsUsingBlock: ^(__strong id obj, NSUInteger idx, BOOL *stop) {
AQBitfield * mask = nil;
+ NSRange range = [obj range];
if ( [masks count] != 0 )
{
- mask = [masks objectAtIndex: idx++];
+ mask = [masks objectAtIndex: idx];
if ( (id)mask == [NSNull null] )
mask = nil;
}
@@ -115,39 +116,52 @@ - (NSComparisonResult) compare: (AQStateMatchingDescriptor *) other
return ( NSOrderedSame );
}
+- (NSString *) description
+{
+ return ( [NSString stringWithFormat: @"%@{uniqueID=%@, matchingIndices=%@}", [super description], _uuid, _matchingIndices] );
+}
+
@end
@implementation AQStateMatchingDescriptor (CreationConvenience)
- (id) initWithRange: (NSRange) range matchingMask: (AQBitfield *) mask
{
- return ( [self initWithRanges: [NSIndexSet indexSetWithIndexesInRange: range]
- matchingMasks: [NSArray arrayWithObject: mask]] );
+ return ( [self initWithRanges: [NSArray arrayWithObject: [[AQRange alloc] initWithRange: range]]
+ matchingMasks: (mask ? [NSArray arrayWithObject: mask] : nil)] );
}
- (id) initWith32BitMask: (NSUInteger) mask forRange: (NSRange) range
{
+ if ( mask == 0 )
+ return ( [self initWithRanges: [NSArray arrayWithObject: [[AQRange alloc] initWithRange: range]]
+ matchingMasks: nil] );
+
AQBitfield * field = [[AQBitfield alloc] init];
for ( NSUInteger i = 0; mask != 0; mask >>= 1, i++ )
{
if ( mask & 1 )
[field setBit: 1 atIndex: i];
}
- return ( [self initWithRanges: [NSIndexSet indexSetWithIndexesInRange: range]
+ return ( [self initWithRanges: [NSArray arrayWithObject: [[AQRange alloc] initWithRange: range]]
matchingMasks: [NSArray arrayWithObject: field]] );
}
- (id) initWith64BitMask: (UInt64) mask forRange: (NSRange) range
{
+ if ( mask == 0 )
+ return ( [self initWithRanges: [NSArray arrayWithObject: [[AQRange alloc] initWithRange: range]]
+ matchingMasks: nil] );
+
AQBitfield * field = [[AQBitfield alloc] init];
for ( UInt64 i = 0; mask != 0; mask >>= 1, i++ )
{
if ( mask & 1 )
[field setBit: 1 atIndex: i];
}
- return ( [self initWithRanges: [NSIndexSet indexSetWithIndexesInRange: range]
+ return ( [self initWithRanges: [NSArray arrayWithObject: [[AQRange alloc] initWithRange: range]]
matchingMasks: [NSArray arrayWithObject: field]] );
}
View
18 AQAppStateMachineTests/AQStateMatchingDescriptorTests.h
@@ -0,0 +1,18 @@
+//
+// AQStateMatchingDescriptorTests.h
+// AQAppStateMachine
+//
+// Created by Jim Dovey on 11-06-22.
+// Copyright 2011 Jim Dovey. All rights reserved.
+//
+// See Also: http://developer.apple.com/iphone/library/documentation/Xcode/Conceptual/iphone_development/135-Unit_Testing_Applications/unit_testing_applications.html
+
+// Application unit tests contain unit test code that must be injected into an application to run correctly.
+// Define USE_APPLICATION_UNIT_TEST to 0 if the unit test code is designed to be linked into an independent test executable.
+
+#import <SenTestingKit/SenTestingKit.h>
+#import <UIKit/UIKit.h>
+
+@interface AQStateMatchingDescriptorTests : SenTestCase
+
+@end
View
56 AQAppStateMachineTests/AQStateMatchingDescriptorTests.m
@@ -0,0 +1,56 @@
+//
+// AQStateMatchingDescriptorTests.m
+// AQAppStateMachine
+//
+// Created by Jim Dovey on 11-06-22.
+// Copyright 2011 Jim Dovey. All rights reserved.
+//
+
+#import "AQStateMatchingDescriptorTests.h"
+#import "AQStateMatchingDescriptor.h"
+#import "AQBitfield.h"
+#import "AQRange.h"
+
+@implementation AQStateMatchingDescriptorTests
+
+- (void) testUniqueIDs
+{
+ AQStateMatchingDescriptor * desc1 = [[AQStateMatchingDescriptor alloc] initWithRange: NSMakeRange(0, 10) matchingMask: nil];
+ AQStateMatchingDescriptor * desc2 = [[AQStateMatchingDescriptor alloc] initWithRange: NSMakeRange(0, 10) matchingMask: nil];
+
+ STAssertTrue([desc1.uniqueID isEqualToString: desc2.uniqueID] == NO, @"Descriptor unique IDs are supposed to be *unique*, dammit!");
+}
+
+- (void) testSingleRangeAndMask
+{
+ AQBitfield * mask = [AQBitfield new];
+ [mask setBitsInRange: NSMakeRange(0, 6) usingBit: 1];
+
+ AQStateMatchingDescriptor * desc = [[AQStateMatchingDescriptor alloc] initWithRange: NSMakeRange(0, 20) matchingMask: mask];
+ STAssertNotNil(desc, @"Expected to be able to at least create an object!");
+
+ NSRange yes = NSMakeRange(0, 6);
+ NSRange no = NSMakeRange(10, 5);
+ STAssertTrue([desc matchesRange: yes], @"Expected range %@ to match descriptor %@", NSStringFromRange(yes), desc);
+ STAssertFalse([desc matchesRange: no], @"Expected range %@ to NOT match descriptor %@", NSStringFromRange(no), desc);
+}
+
+- (void) testMultipleRangesWithMasks
+{
+ NSRange range1 = NSMakeRange(0, 20);
+ AQBitfield * mask1 = [AQBitfield new];
+ [mask1 setBitsInRange: NSMakeRange(0, 6) usingBit: 1];
+
+ NSRange range2 = NSMakeRange(25, 5);
+ AQBitfield * mask2 = [AQBitfield new];
+ [mask2 setBitsInRange: NSMakeRange(0, 4) usingBit: 1];
+
+ AQStateMatchingDescriptor * desc = [[AQStateMatchingDescriptor alloc] initWithRanges: [NSArray arrayWithObjects: [[AQRange alloc] initWithRange: range1], [[AQRange alloc] initWithRange: range2], nil] matchingMasks: [NSArray arrayWithObjects: mask1, mask2, nil]];
+
+ NSRange yes = NSMakeRange(0, 6);
+ NSRange no = NSMakeRange(29, 1);
+ STAssertTrue([desc matchesRange: yes], @"Expected range %@ to match descriptor %@", NSStringFromRange(yes), desc);
+ STAssertFalse([desc matchesRange: no], @"Expected range %@ to NOT match descriptor %@", NSStringFromRange(no), desc);
+}
+
+@end

0 comments on commit cf4417a

Please sign in to comment.