Project import
diff --git a/OCCukes/OCCucumber.h b/OCCukes/OCCucumber.h new file mode 100755 index 0000000..929bb0f --- /dev/null +++ b/OCCukes/OCCucumber.h
@@ -0,0 +1,80 @@ +// OCCukes OCCucumber.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +@class OCCucumberWorld; + +/** + * Offers a set of class-scoped convenience methods for interacting with + * Cucumber language and runtime. + * + * Typically, your unit tests will interact via this interface, rather + * than via the Cucumber shared language, or some other instance of the language + * class. + * + * All the class-scoped methods access the shared runtime's language, rather + * than the shared language. Typically this amounts to exactly the same + * thing. The answer differs only in one case: when the runtime overrides the + * language. In that case, the runtime takes precedence. + */ +@interface OCCucumber : NSObject + +//----------------------------------------------------------- Language Shortcuts + ++ (void)given:(NSString *)pattern step:(void (^)(NSArray *arguments))block; ++ (void)when:(NSString *)pattern step:(void (^)(NSArray *arguments))block; ++ (void)then:(NSString *)pattern step:(void (^)(NSArray *arguments))block; + ++ (void)given:(NSString *)pattern + step:(void (^)(NSArray *arguments))block + file:(const char *)file + line:(unsigned int)line; ++ (void)when:(NSString *)pattern + step:(void (^)(NSArray *arguments))block + file:(const char *)file + line:(unsigned int)line; ++ (void)then:(NSString *)pattern + step:(void (^)(NSArray *arguments))block + file:(const char *)file + line:(unsigned int)line; + +/** + * Answers the shared language's current world, a shortcut. + * + * World's appear and disappear as scenarios begin and end. Step + * definitions use them as scratchpads for persisting arguments and other pieces + * of information in-between steps. Use key-value coding to access world values. + */ ++ (OCCucumberWorld *)currentWorld; + ++ (void)step:(NSString *)name arguments:(NSArray *)arguments; ++ (void)step:(NSString *)name; + +//------------------------------------------------------------ Runtime Shortcuts + ++ (void)pending; ++ (void)pending:(NSString *)message; + +@end
diff --git a/OCCukes/OCCucumber.m b/OCCukes/OCCucumber.m new file mode 100755 index 0000000..c8c8ccb --- /dev/null +++ b/OCCukes/OCCucumber.m
@@ -0,0 +1,106 @@ +// OCCukes OCCucumber.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumber.h" +#import "OCCucumberRuntime.h" +#import "OCCucumberLanguage.h" +#import "OCCucumberWorld.h" +#import "OCCucumberStepDefinition.h" + +@implementation OCCucumber + ++ (void)given:(NSString *)pattern step:(void (^)(NSArray *arguments))block +{ + [[[OCCucumberRuntime sharedRuntime] language] registerStepPattern:pattern block:block]; +} + ++ (void)when:(NSString *)pattern step:(void (^)(NSArray *arguments))block +{ + [[[OCCucumberRuntime sharedRuntime] language] registerStepPattern:pattern block:block]; +} + ++ (void)then:(NSString *)pattern step:(void (^)(NSArray *arguments))block +{ + [[[OCCucumberRuntime sharedRuntime] language] registerStepPattern:pattern block:block]; +} + ++ (void)given:(NSString *)pattern + step:(void (^)(NSArray *arguments))block + file:(const char *)file + line:(unsigned int)line +{ + OCCucumberLanguage *language = [[OCCucumberRuntime sharedRuntime] language]; + OCCucumberStepDefinition *stepDefinition = [language registerStepPattern:pattern block:block]; + [stepDefinition setFile:file]; + [stepDefinition setLine:line]; +} + ++ (void)when:(NSString *)pattern + step:(void (^)(NSArray *arguments))block + file:(const char *)file + line:(unsigned int)line +{ + OCCucumberLanguage *language = [[OCCucumberRuntime sharedRuntime] language]; + OCCucumberStepDefinition *stepDefinition = [language registerStepPattern:pattern block:block]; + [stepDefinition setFile:file]; + [stepDefinition setLine:line]; +} + ++ (void)then:(NSString *)pattern + step:(void (^)(NSArray *arguments))block + file:(const char *)file + line:(unsigned int)line +{ + OCCucumberLanguage *language = [[OCCucumberRuntime sharedRuntime] language]; + OCCucumberStepDefinition *stepDefinition = [language registerStepPattern:pattern block:block]; + [stepDefinition setFile:file]; + [stepDefinition setLine:line]; +} + ++ (OCCucumberWorld *)currentWorld +{ + return [[[OCCucumberRuntime sharedRuntime] language] currentWorld]; +} + ++ (void)step:(NSString *)name arguments:(NSArray *)arguments +{ + [[[OCCucumberRuntime sharedRuntime] language] step:name arguments:arguments]; +} + ++ (void)step:(NSString *)name +{ + [self step:name arguments:nil]; +} + ++ (void)pending +{ + @throw [NSArray arrayWithObject:@"pending"]; +} + ++ (void)pending:(NSString *)message +{ + @throw [NSArray arrayWithObjects:@"pending", message, nil]; +} + +@end
diff --git a/OCCukes/OCCucumberException.h b/OCCukes/OCCucumberException.h new file mode 100644 index 0000000..7d9af5a --- /dev/null +++ b/OCCukes/OCCucumberException.h
@@ -0,0 +1,5 @@ +#import <Foundation/Foundation.h> + +@interface OCCucumberException : NSException + +@end
diff --git a/OCCukes/OCCucumberException.m b/OCCukes/OCCucumberException.m new file mode 100644 index 0000000..1ba7318 --- /dev/null +++ b/OCCukes/OCCucumberException.m
@@ -0,0 +1,5 @@ +#import "OCCucumberException.h" + +@implementation OCCucumberException + +@end
diff --git a/OCCukes/OCCucumberExceptions.h b/OCCukes/OCCucumberExceptions.h new file mode 100755 index 0000000..3e7ec0a --- /dev/null +++ b/OCCukes/OCCucumberExceptions.h
@@ -0,0 +1,29 @@ +/* OCCukes OCCucumberExceptions.h + * + * Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO + * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ******************************************************************************/ + +#import <Foundation/Foundation.h> + +extern NSString *const OCCucumberUndefinedException; +extern NSString *const OCCucumberPendingException; +extern NSString *const OCCucumberAmbiguousException;
diff --git a/OCCukes/OCCucumberExceptions.m b/OCCukes/OCCucumberExceptions.m new file mode 100755 index 0000000..c99bc86 --- /dev/null +++ b/OCCukes/OCCucumberExceptions.m
@@ -0,0 +1,29 @@ +/* OCCukes OCCucumberExceptions.m + * + * Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO + * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ******************************************************************************/ + +#import "OCCucumberExceptions.h" + +NSString *const OCCucumberUndefinedException = @"OCCucumberUndefinedException"; +NSString *const OCCucumberPendingException = @"OCCucumberPendingException"; +NSString *const OCCucumberAmbiguousException = @"OCCucumberAmbiguousException";
diff --git a/OCCukes/OCCucumberLanguage.h b/OCCukes/OCCucumberLanguage.h new file mode 100755 index 0000000..4e3b94d --- /dev/null +++ b/OCCukes/OCCucumberLanguage.h
@@ -0,0 +1,65 @@ +// OCCukes OCCucumberLanguage.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +@class OCCucumberWorld; +@class OCCucumberStepDefinition; + +@interface OCCucumberLanguage : NSObject + +@property(strong, NS_NONATOMIC_IOSONLY) OCCucumberWorld *currentWorld; +@property(strong, NS_NONATOMIC_IOSONLY) NSMutableSet *stepDefinitions; + +- (void)registerStepDefinition:(OCCucumberStepDefinition *)stepDefinition; +- (OCCucumberStepDefinition *)registerStep:(NSRegularExpression *)regularExpression block:(void (^)(NSArray *arguments))block; +- (OCCucumberStepDefinition *)registerStepPattern:(NSString *)pattern block:(void (^)(NSArray *arguments))block; + +/** + * Answers the steps matching a given step name. + * + * There could be more than one. The resulting array contains Step + * Match objects. Each Step Match retains its step definition for later + * invocation and the argument values derived from the match. + */ +- (NSArray *)stepMatches:(NSString *)nameToMatch; + +- (void)beginScenario; +- (void)endScenario; + +/** + * Invokes a single step. + * + * Be careful when invoking steps. Make sure you do not invoke yourself + * and recurse infinitely. + * + * @exception Raises an ambiguity exception if more than one match. Raises an + * undefined exception if no match. + */ +- (void)step:(NSString *)name arguments:(NSArray *)arguments; +- (void)step:(NSString *)name; + ++ (OCCucumberLanguage *)sharedLanguage; + +@end
diff --git a/OCCukes/OCCucumberLanguage.m b/OCCukes/OCCucumberLanguage.m new file mode 100755 index 0000000..1993453 --- /dev/null +++ b/OCCukes/OCCucumberLanguage.m
@@ -0,0 +1,129 @@ +// OCCukes OCCucumberLanguage.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberLanguage.h" +#import "OCCucumberWorld.h" +#import "OCCucumberStepDefinition.h" +#import "OCCucumberStepMatch.h" +#import "OCCucumberStepArgument.h" +#import "OCCucumberExceptions.h" + +@implementation OCCucumberLanguage + +@synthesize currentWorld = _currentWorld; +@synthesize stepDefinitions = _stepDefinitions; + +// designated initialiser +- (id)init +{ + if ((self = [super init])) + { + [self setStepDefinitions:[NSMutableSet set]]; + } + return self; +} + +- (void)registerStepDefinition:(OCCucumberStepDefinition *)stepDefinition +{ + [[self stepDefinitions] addObject:stepDefinition]; +} + +- (OCCucumberStepDefinition *)registerStep:(NSRegularExpression *)regularExpression block:(void (^)(NSArray *arguments))block +{ + OCCucumberStepDefinition *stepDefinition = [[OCCucumberStepDefinition alloc] initWithRegularExpression:regularExpression block:block]; + [self registerStepDefinition:stepDefinition]; + return stepDefinition; +} + +- (OCCucumberStepDefinition *)registerStepPattern:(NSString *)pattern block:(void (^)(NSArray *arguments))block +{ + OCCucumberStepDefinition *stepDefinition = [[OCCucumberStepDefinition alloc] initWithPattern:pattern block:block]; + [self registerStepDefinition:stepDefinition]; + return stepDefinition; +} + +- (NSArray *)stepMatches:(NSString *)nameToMatch +{ + NSMutableArray *matches = [NSMutableArray array]; + for (OCCucumberStepDefinition *stepDefinition in [self stepDefinitions]) + { + NSArray *arguments = [stepDefinition argumentsFromStepName:nameToMatch]; + if (arguments) + { + [matches addObject:[[OCCucumberStepMatch alloc] initWithDefinition:stepDefinition arguments:arguments]]; + } + } + return [matches copy]; +} + +- (void)beginScenario +{ + [self setCurrentWorld:[[OCCucumberWorld alloc] init]]; +} + +- (void)endScenario +{ + [self setCurrentWorld:nil]; +} + +- (void)step:(NSString *)name arguments:(NSArray *)arguments +{ + NSArray *stepMatches = [self stepMatches:name]; + if ([stepMatches count] == 0) + { + [NSException raise:OCCucumberUndefinedException format:@"Undefined step \"%@\"", name]; + } + if ([stepMatches count] > 1) + { + [NSException raise:OCCucumberAmbiguousException format:@"Ambiguous match of \"%@\"", name]; + } + OCCucumberStepMatch *stepMatch = [stepMatches objectAtIndex:0]; + NSMutableArray *values = [NSMutableArray array]; + for (OCCucumberStepArgument *stepArgument in [stepMatch stepArguments]) + { + [values addObject:[stepArgument value]]; + } + if (arguments) + { + [values addObjectsFromArray:arguments]; + } + [[stepMatch stepDefinition] invokeWithArguments:[values copy]]; +} + +- (void)step:(NSString *)name +{ + [self step:name arguments:nil]; +} + ++ (OCCucumberLanguage *)sharedLanguage +{ + static OCCucumberLanguage *__strong sharedLanguage; + if (sharedLanguage == nil) + { + sharedLanguage = [[OCCucumberLanguage alloc] init]; + } + return sharedLanguage; +} + +@end
diff --git a/OCCukes/OCCucumberRuntime+WireProtocol.h b/OCCukes/OCCucumberRuntime+WireProtocol.h new file mode 100755 index 0000000..33ec074 --- /dev/null +++ b/OCCukes/OCCucumberRuntime+WireProtocol.h
@@ -0,0 +1,72 @@ +// OCCukes OCCucumberRuntime+WireProtocol.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberRuntime.h" + +extern NSString *const OCCucumberRuntimeUnhandledStepNotification; + extern NSString *const OCCucumberRuntimeUnhandledStepNameKey; + +@interface OCCucumberRuntime(WireProtocol) + +- (id)handleWirePacketWithObject:(id)object; +- (id)handleWirePacketWithArray:(NSArray *)array; + +/** + * Finds out whether the wire server has a definition for a given step. + * + * When features have been parsed, Cucumber will send a "step_matches" + * message to ask the wire server if it can match a step name. This happens for + * each of the steps in each of the features. + * + * @result The wire server replies with an array of Step Match objects. + * Each Step Match hash element comprises the following key-value pairs. + * - An "id" string identifier for the step definition to be referenced later + * when it needs to be invoked. The identifier can be any string value and is + * simply used for wire server's own reference. + * - An "args" array for any argument values captured by the wire end's own + * regular expression (or other argument matching) process. + */ +- (id)handleStepMatchesWithHash:(NSDictionary *)hash; + +/** + * Requests a snippet for an undefined step. + */ +- (id)handleSnippetTextWithHash:(NSDictionary *)hash; + +/** + * Signals that Cucumber is about to execute a scenario. + */ +- (id)handleBeginScenario; + +/** + * Signals that Cucumber has finished executing a scenario. + */ +- (id)handleEndScenario; + +/** + * Asks for a step definition to be invoked. + */ +- (id)handleInvokeWithHash:(NSDictionary *)hash; + +@end
diff --git a/OCCukes/OCCucumberRuntime+WireProtocol.m b/OCCukes/OCCucumberRuntime+WireProtocol.m new file mode 100755 index 0000000..1c78625 --- /dev/null +++ b/OCCukes/OCCucumberRuntime+WireProtocol.m
@@ -0,0 +1,226 @@ +// OCCukes OCCucumberRuntime+WireProtocol.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberRuntime+WireProtocol.h" +#import "OCCucumberLanguage.h" +#import "OCCucumberStepDefinition.h" +#import "OCCucumberStepMatch.h" +#import "OCCucumberStepArgument.h" +#import "OCCucumberException.h" + +// Semantic issue: performSelector may cause a leak because its selector is +// unknown! Ignore this "ARC performSelector leaks" warning. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + +NSString *const OCCucumberRuntimeUnhandledStepNotification = @"OCCucumberRuntimeUnhandledStep"; + NSString *const OCCucumberRuntimeUnhandledStepNameKey = @"OCCucumberRuntimeUnhandledStepName"; + +NSString *__OCCucumberRuntimeCamelize(NSString *string); + +@implementation OCCucumberRuntime(WireProtocol) + +- (id)handleWirePacketWithObject:(id)object +{ + // By default, all paths through the demultiplexer answer nil. All the + // handler methods follow this same outline: set up a default nil result + // then attempt to handle the wire packet. + id result = nil; + if ([object isKindOfClass:[NSArray class]]) + { + result = [self handleWirePacketWithArray:(NSArray *)object]; + } + return result; +} + +- (id)handleWirePacketWithArray:(NSArray *)array +{ + id result = nil; + switch ([array count]) + { + case 1: + { + SEL selector = NSSelectorFromString([NSString stringWithFormat:@"handle%@", __OCCucumberRuntimeCamelize([array objectAtIndex:0])]); + if ([self respondsToSelector:selector]) + { + result = [self performSelector:selector]; + } + break; + } + case 2: + { + // Handle wire packets consisting of two array elements. The first + // element describes the message. The second element, either an + // array or a hash, describes the message parameter. Invoke the + // runtime with a selector of the form -handle<Message>WithArray: or + // -handle<Message>WithHash: passing an array or a dictionary, + // respectively. Ignore messages where there is no corresponding + // selector. + NSString *with; + id object = [array objectAtIndex:1]; + if ([object isKindOfClass:[NSArray class]]) + { + with = @"Array"; + } + else if ([object isKindOfClass:[NSDictionary class]]) + { + with = @"Hash"; + } + else + { + with = nil; + } + if (with) + { + SEL selector = NSSelectorFromString([NSString stringWithFormat:@"handle%@With%@:", __OCCucumberRuntimeCamelize([array objectAtIndex:0]), with]); + if ([self respondsToSelector:selector]) + { + result = [self performSelector:selector withObject:object]; + } + } + } + } + return result; +} + +- (id)handleStepMatchesWithHash:(NSDictionary *)hash +{ + NSMutableArray *stepMatches = [NSMutableArray array]; + NSString *nameToMatch = [hash objectForKey:@"name_to_match"]; + for (OCCucumberStepMatch *match in [[self language] stepMatches:nameToMatch]) + { + OCCucumberStepDefinition *stepDefinition = [match stepDefinition]; + + // The "args" response is an array of hashes. Each hash has a "val" and + // "pos" key specifying the argument's value and position. + NSMutableArray *args = [NSMutableArray array]; + for (OCCucumberStepArgument *argument in [match stepArguments]) + { + [args addObject:[NSDictionary dictionaryWithObjectsAndKeys:[argument value], @"val", [NSNumber numberWithUnsignedInteger:[argument offset]], @"pos", nil]]; + } + + const char *file = [stepDefinition file]; + NSString *source = file ? [NSString stringWithFormat:@"%s:%u", file, [stepDefinition line]] : nil; + [stepMatches addObject:[NSDictionary dictionaryWithObjectsAndKeys:[stepDefinition identifierString], @"id", args, @"args", source, @"source", nil]]; + } + + if (stepMatches.count == 0) { + [self notifyAboutUnhandledStepWithName:nameToMatch]; + } + + return [NSArray arrayWithObjects:@"success", [stepMatches copy], nil]; +} + +- (id)handleSnippetTextWithHash:(NSDictionary *)hash +{ + NSString *stepKeyword = [hash objectForKey:@"step_keyword"]; + NSString *stepName = [hash objectForKey:@"step_name"]; + + // Handle argument patterns: double-quoted strings and digit + // sequences. Note, the question mark in *? means match zero + // or more times and match as few times as possible. + NSMutableString *snippetPattern = [NSMutableString stringWithString:[NSRegularExpression escapedPatternForString:stepName]]; + for (NSString *pattern in [NSArray arrayWithObjects:@"\"(.*?)\"", @"(\\d+)", nil]) + { + NSError *__autoreleasing error = nil; + NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; + [regularExpression replaceMatchesInString:snippetPattern options:0 range:NSMakeRange(0, [snippetPattern length]) withTemplate:[NSRegularExpression escapedPatternForString:pattern]]; + } + [snippetPattern replaceOccurrencesOfString:@"\\" withString:@"\\\\" options:0 range:NSMakeRange(0, [snippetPattern length])]; + [snippetPattern replaceOccurrencesOfString:@"\"" withString:@"\\\"" options:0 range:NSMakeRange(0, [snippetPattern length])]; + + return [NSArray arrayWithObjects:@"success", [NSString stringWithFormat:@"\t[OCCucumber %@:@\"^%@$\" step:^(NSArray *arguments) {\n\t\t// express the regular expression above with the code you wish you had\n\t\t[OCCucumber pending:@\"TODO\"];\n\t} file:__FILE__ line:__LINE__];", [stepKeyword lowercaseString], [snippetPattern copy]], nil]; +} + +- (id)handleBeginScenario +{ + [[self language] beginScenario]; + return [NSArray arrayWithObject:@"success"]; +} + +- (id)handleEndScenario +{ + [[self language] endScenario]; + return [NSArray arrayWithObject:@"success"]; +} + +- (id)handleInvokeWithHash:(NSDictionary *)hash +{ + id result = nil; + NSString *identifierString = [hash objectForKey:@"id"]; + for (OCCucumberStepDefinition *stepDefinition in [[self language] stepDefinitions]) + { + if ([[stepDefinition identifierString] isEqualToString:identifierString]) + { + // The step block throws any object in order to respond. If the wire + // server can successfully convert the thrown object to JSON, it + // becomes the reply. If the step does not raise an exception, + // answer with success. + // + // Catch NextStep exceptions. Such an exception gives a Cucumber + // failure result, where the exception reason becomes the failure + // message. + @try + { + [stepDefinition invokeWithArguments:[hash objectForKey:@"args"]]; + result = [NSArray arrayWithObject:@"success"]; + } + @catch (OCCucumberException *exception) + { + result = [NSArray arrayWithObjects:@"fail", [NSDictionary dictionaryWithObjectsAndKeys:[exception reason], @"message", [exception name], @"exception", nil], nil]; + } + break; + } + } + return result; +} + +- (void)notifyAboutUnhandledStepWithName:(NSString *)unhadledStepName { + NSDictionary *userInfo = @{OCCucumberRuntimeUnhandledStepNameKey: unhadledStepName}; + NSNotification *notification = [NSNotification notificationWithName:OCCucumberRuntimeUnhandledStepNotification + object:self + userInfo:userInfo]; + + [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; +} + +@end + +/* + * Converts a string with underscore delimiters to camel-case with leading + * capital letter. Useful for deriving selectors from wire protocol messages, + * e.g. for converting begin_scenario to BeginScenario. + */ +NSString *__OCCucumberRuntimeCamelize(NSString *string) +{ + NSMutableArray *components = [NSMutableArray array]; + for (NSString *component in [string componentsSeparatedByString:@"_"]) + { + [components addObject:[component capitalizedString]]; + } + return [components componentsJoinedByString:@""]; +} + +// arc-performSelector-leaks ignored +#pragma clang diagnostic pop
diff --git a/OCCukes/OCCucumberRuntime.h b/OCCukes/OCCucumberRuntime.h new file mode 100644 index 0000000..182c824 --- /dev/null +++ b/OCCukes/OCCucumberRuntime.h
@@ -0,0 +1,110 @@ +// OCCukes OCCucumberRuntime.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +#import <CFSockets/CFSockets.h> + +@class OCCucumberLanguage; + +/** + * Ties everything together, the meaty part. + * + * You set up and tear down the run-time at run time. The runtime + * accepts socket connections from Cucumber. + */ +@interface OCCucumberRuntime : NSObject <CFSocketDelegate, CFStreamPairDelegate> + +@property(strong, NS_NONATOMIC_IOSONLY) OCCucumberLanguage *language; + +@property(assign, NS_NONATOMIC_IOSONLY) NSTimeInterval connectTimeout; +@property(assign, NS_NONATOMIC_IOSONLY) NSTimeInterval disconnectTimeout; + +/** + * Runtime address and port. Default ones are 0.0.0.0:31337 + */ +@property(strong, NS_NONATOMIC_IOSONLY) NSString *address; +@property(assign, NS_NONATOMIC_IOSONLY) NSUInteger port; + +/** + * Date when the runtime expires. + * + * Expiring date describes when the runtime should stop running. While + * connections exist, the runtime never expires, or expires in the distant + * future to be more precise. The runtime expires after the disconnect timeout + * after all connections disconnect. It also expires after the connect timeout + * after setting up the runtime. + */ +@property(strong, NS_NONATOMIC_IOSONLY) NSDate *expiresDate; + +/** + * Answers all the current connections as a set. + * + * Connections change dynamically as remote Cucumber clients connect to + * and disconnect from the wire server. + */ +- (NSSet *)allConnections; + +- (void)setUp; + +- (void)setUpWithPort:(int)port serviceType:(NSString *)serviceType; + +- (void)tearDown; + +/** + * Cucumber runtime runs, naturally. + * + * When running the wire server, there are two timing + * requirements. First, wait at least for a nominal 10 seconds before giving up + * on taking a connection. Call this the "connect timeout" period; it defines + * the maximum delay in-between setting up the server and making the first + * wire-client connection. There must be at least one incoming Cucumber + * connection to disable this timeout. When a connection activates, the timeout + * becomes infinite. Provided at least one connection remains, the server + * continues running indefinitely. When the last connection closes, the wire + * server sustains the server socket for one more second before exiting. This + * allows for execution of Cucumber commands over multiple socket connections in + * rapid sequence. Call this the disconnect timeout. You can adjust the connect + * and disconnect timeouts accordingly. + */ +- (void)run; + +/** + * Answers YES if the runtime wants to continue running. + * + * The runtime continues running while connections exist, or the + * current time lies within connection and disconnection timeout + * periods. Outside these conditions, the runtime will continue to accept new + * connections if you ignore the running status. In such event, the is-running + * status will revert to YES when incoming new connections establish. + */ +- (BOOL)isRunning; + ++ (OCCucumberRuntime *)sharedRuntime; + +@end + +extern NSString *const OCCucumberRuntimeConnectNotification; +extern NSString *const OCCucumberRuntimeDisconnectNotification; +extern NSString *const OCCucumberRuntimeWirePairKey; \ No newline at end of file
diff --git a/OCCukes/OCCucumberRuntime.m b/OCCukes/OCCucumberRuntime.m new file mode 100644 index 0000000..9d556ce --- /dev/null +++ b/OCCukes/OCCucumberRuntime.m
@@ -0,0 +1,255 @@ +// OCCukes OCCucumberRuntime.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <arpa/inet.h> +#import <ifaddrs.h> +#import "OCCucumberRuntime.h" +#import "OCCucumberRuntime+WireProtocol.h" +#import "OCCucumberLanguage.h" + +static const int DefaultPort = 31337; + +@interface OCCucumberRuntime () + +// The wire socket accepts connections on a prescribed host and port, typically +// local host, port 54321. Wire pairs represent a set of Core Foundation stream +// pairs for individual connections to remote wire clients, typically Ruby +// interpreters running Cucumber. Wire pairs appear when the wire socket accepts +// a new connection and disappear when the remote Cucumber client closes the +// connection. +@property(strong, NS_NONATOMIC_IOSONLY) CFSocket *wireSocket; +@property(strong, NS_NONATOMIC_IOSONLY) NSMutableSet *wirePairs; +@property(strong, NS_NONATOMIC_IOSONLY) NSNetService *netService; + +@end + +@implementation OCCucumberRuntime + +@synthesize language = _language; +@synthesize connectTimeout = _connectTimeout; +@synthesize disconnectTimeout = _disconnectTimeout; + +@synthesize expiresDate = _expiresDate; + +@synthesize wireSocket = _wireSocket; +@synthesize wirePairs = _wirePairs; +@synthesize netService = _netService; + +- (void)setLanguage:(OCCucumberLanguage *)language { + _language = language; +} + +- (OCCucumberLanguage *)language { + // You can override the runtime instance's language. By default, if language + // equals nil, the runtime picks up the shared language. + return _language ? _language : [OCCucumberLanguage sharedLanguage]; +} + +- (NSSet *)allConnections { + return [[self wirePairs] copy]; +} + +- (id)init { + if ((self = [super init])) { + [self setConnectTimeout:10.0]; + [self setDisconnectTimeout:1.0]; + + self.port = DefaultPort; + self.address = nil; + } + return self; +} + +- (void)setUp { + if (self.address.length > 0) { + NSLog(@"Starting OCCucumber runtime [%@ :%zd]", self.address, self.port); + + [self setUpWithAddress:self.address port:self.port]; + } else { + NSLog(@"Starting OCCucumber runtime [0.0.0.0:%zd]", self.port); + + [self setUpWithPort:(int)self.port]; + } +} + +- (void)setUpWithAddress:(NSString *)address port:(NSUInteger)port { + in_addr_t addr; + inet_pton(AF_INET, [address cStringUsingEncoding:NSUTF8StringEncoding], &addr); + + [self setUpWithSocketAddressData:CFSocketAddressDataFromIPv4AddressWithPort(addr, (in_port_t) port)]; +} + +- (void)setUpWithPort:(int)port { + [self setUpWithSocketAddressData:CFSocketAddressDataFromIPv4AddressWithPort(INADDR_ANY, (in_port_t) port)]; +} + +- (void)setUpWithSocketAddressData:(NSData *)socketAddress { + CFSocket *socket = [[CFSocket alloc] initForTCPv4]; + [socket setDelegate:self]; + [socket setReuseAddressOption:YES]; + [socket setAddress:socketAddress error:NULL]; + [socket addToCurrentRunLoopForCommonModes]; + + [self setWireSocket:socket]; + [self setWirePairs:[NSMutableSet set]]; + [self setExpiresDate:[NSDate dateWithTimeIntervalSinceNow:[self connectTimeout]]]; +} + +- (void)setUpWithPort:(int)port serviceType:(NSString *)serviceType { + CFSocket *socket = [[CFSocket alloc] initForTCPv6]; + [socket setDelegate:self]; + [socket setReuseAddressOption:YES]; + [socket setAddress:CFSocketAddressDataFromAnyIPv6WithPort(port) error:NULL]; + [socket addToCurrentRunLoopForCommonModes]; + + [self setWireSocket:socket]; + [self setWirePairs:[NSMutableSet set]]; + [self setExpiresDate:[NSDate dateWithTimeIntervalSinceNow:[self connectTimeout]]]; + + // Publish the Cucumber runtime as a "_occukes-runtime._tcp." network + // service type. Application protocol name must be an underscore plus 1-15 + // characters. See http://www.dns-sd.org/ServiceTypes.html for examples. + [self setNetService:[[NSNetService alloc] initWithDomain:@"" type:[NSString stringWithFormat:@"_%@._tcp.", serviceType] name:@"" port:[socket port]]]; + if ([self netService]) { + [[self netService] publish]; + } +} + +- (void)tearDown { + // Removing all connections automatically closes the connections because the + // request-response stream pair closes on deallocation. Therefore, no need + // to explicitly close every pair, as follows. Releasing the socket shuts + // down the socket. Releasing the pair set releases and shuts down all the + // wire connections. + // + // for (CFStreamPair *wirePair in [self wirePairs]) + // { + // [wirePair close]; + // } + // + [self setWireSocket:nil]; + [self setWirePairs:nil]; + [self setExpiresDate:nil]; + + // Stop publishing the network service. + if ([self netService]) { + [[self netService] stop]; + [self setNetService:nil]; + } +} + +- (void)run { + do { + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + while ([self isRunning]); +} + +- (BOOL)isRunning { + NSDate *expiresDate = [self expiresDate]; + return expiresDate && [expiresDate compare:[NSDate date]] == NSOrderedDescending; +} + +- (void)socket:(CFSocket *)socket acceptStreamPair:(CFStreamPair *)streamPair { + // Accepts new connections. Attaches a stream-pair to each incoming + // connection. Retains, delegates and opens the new wire pair. + // The wire server opens one "wire" for each connection, each Cucumber + // client. The wire connection's stream-pair encapsulates client-server + // interactions. It decodes wire packets from the request stream and encodes + // wire packets to the response stream. + if ([[self wirePairs] count] == 0) { + [self setExpiresDate:[NSDate distantFuture]]; + } + [[self wirePairs] addObject:streamPair]; + [streamPair setDelegate:self]; + [streamPair open]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:streamPair forKey:OCCucumberRuntimeWirePairKey]; + NSNotification *notification = [NSNotification notificationWithName:OCCucumberRuntimeConnectNotification object:self userInfo:userInfo]; + [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; +} + +- (void)streamPair:(CFStreamPair *)streamPair hasBytesAvailable:(NSUInteger)bytesAvailable { + // The wire protocol begins by quantising the request messages by line + // terminator. One line equates to one message, or wire packet. The request + // is a JSON array. The first element specifies the message, one of: + // step_matches, invoke, begin_scenario, end_scenario, or snippet_text. The + // second array element specifies the parameters, a hash or array depending + // on the message. Demultiplex the JSON request and invoke the corresponding + // handler. + NSString *line; + while ((line = [streamPair receiveLineUsingEncoding:NSUTF8StringEncoding])) { + NSError *__autoreleasing error = nil; + id object = [NSJSONSerialization JSONObjectWithData:[line dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error]; + if (object) { + id result = [self handleWirePacketWithObject:object]; + // Always answer with something whenever the request decodes valid + // JSON. Send valid JSON back because at the other end of the + // connection likely sits a Cucumber instance. Send a Cucumber + // wire-protocol failure packet. + if (result == nil) { + result = [NSArray arrayWithObject:@"fail"]; + } + NSData *data = [NSJSONSerialization dataWithJSONObject:result options:0 error:&error]; + if (data) { + [streamPair sendBytes:data]; + [streamPair sendBytes:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]]; + } + } + } +} + +- (void)streamPair:(CFStreamPair *)streamPair handleRequestEvent:(NSStreamEvent)eventCode { + switch (eventCode) { + case NSStreamEventEndEncountered: { + // Enqueue the disconnect notification. It does not post + // immediately. By the time the notification arrives at its + // destination, or destinations, the stream pair no longer appears + // as a wire pair; and if it was the very last wire pair, the + // expires date reflects the disconnect timeout since now. + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:streamPair forKey:OCCucumberRuntimeWirePairKey]; + NSNotification *notification = [NSNotification notificationWithName:OCCucumberRuntimeDisconnectNotification object:self userInfo:userInfo]; + [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; + [[self wirePairs] removeObject:streamPair]; + if ([[self wirePairs] count] == 0) { + [self setExpiresDate:[NSDate dateWithTimeIntervalSinceNow:[self disconnectTimeout]]]; + } + break; + } + default:; + } +} + ++ (OCCucumberRuntime *)sharedRuntime { + static OCCucumberRuntime *__strong sharedRuntime; + if (sharedRuntime == nil) { + sharedRuntime = [[OCCucumberRuntime alloc] init]; + } + return sharedRuntime; +} + +@end + +NSString *const OCCucumberRuntimeConnectNotification = @"OCCucumberRuntimeConnect"; +NSString *const OCCucumberRuntimeDisconnectNotification = @"OCCucumberRuntimeDisconnect"; +NSString *const OCCucumberRuntimeWirePairKey = @"OCCucumberRuntimeWirePair";
diff --git a/OCCukes/OCCucumberStepArgument.h b/OCCukes/OCCucumberStepArgument.h new file mode 100755 index 0000000..28e2c27 --- /dev/null +++ b/OCCukes/OCCucumberStepArgument.h
@@ -0,0 +1,32 @@ +// OCCukes OCCucumberStepArgument.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +@interface OCCucumberStepArgument : NSObject + +@property(assign, NS_NONATOMIC_IOSONLY) NSUInteger offset; +@property(strong, NS_NONATOMIC_IOSONLY) NSString *value; + +@end
diff --git a/OCCukes/OCCucumberStepArgument.m b/OCCukes/OCCucumberStepArgument.m new file mode 100755 index 0000000..59cf3de --- /dev/null +++ b/OCCukes/OCCucumberStepArgument.m
@@ -0,0 +1,29 @@ +// OCCukes OCCucumberStepArgument.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberStepArgument.h" + +@implementation OCCucumberStepArgument + +@end
diff --git a/OCCukes/OCCucumberStepDefinition.h b/OCCukes/OCCucumberStepDefinition.h new file mode 100755 index 0000000..738974a --- /dev/null +++ b/OCCukes/OCCucumberStepDefinition.h
@@ -0,0 +1,94 @@ +// OCCukes OCCucumberStepDefinition.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +/** + * Step definitions hold a regular expression and a C block. + * + * You create a step definition by registering a Give, When or Then + * step. You can invoke a step definition. + * + * Step definitions carry a file and line property. Assign these to `__FILE__` + * and `__LINE__` respectively. Cucumber reports source file-colon-line + * references for step definitions. These become clickable in editors such as + * TextMate and RubyMine. Very useful when developing. Note that `__FILE__` is a + * pointer to a static string of characters. No need to copy the static + * null-terminated C string. As a static, the string will remain in place and + * unchanged. Line numbers are ordinal, rather than cardinal. Line 1 is the + * first line, not the second line. Hence line 0 means line number unspecified. + */ +@interface OCCucumberStepDefinition : NSObject + +@property(strong, NS_NONATOMIC_IOSONLY) NSRegularExpression *regularExpression; +@property(copy, NS_NONATOMIC_IOSONLY) void (^block)(NSArray *arguments); +@property(assign, NS_NONATOMIC_IOSONLY) const char *file; +@property(assign, NS_NONATOMIC_IOSONLY) unsigned int line; + +// convenience initialisers +- (id)initWithRegularExpression:(NSRegularExpression *)regularExpression block:(void (^)(NSArray *arguments))block; + +/** + * Constructs a new step definition from a given pattern string. + * + * Compiles a new regular expression for the step based on the given + * pattern string. Answers a new step definition if the expression successfully + * compiles. Otherwise answers @c nil. + */ +- (id)initWithPattern:(NSString *)pattern block:(void (^)(NSArray *arguments))block; + +/** + * Answers a unique string identifier for the step definition. + * + * All step definitions need a unique string identifier. The Cucumber + * client utilises these identifiers in order to reference remote step + * definitions across the wire. Identifiers are string values. So long as they + * are one-to-one with the identity of the step definition, and not the state of + * the step definition, their exact contents are arbitrary. The implementation + * uses a string value based on the "self" pointer. + */ +- (NSString *)identifierString; + +/** + * @result Answers an array of regular-expression match capture values, one for + * each matching capture group. Answers @c nil if the step definition's regular + * expression pattern does not match the given step name. Answers an array of + * zero length if the pattern matches but no capture groups exist in the step + * expression. + */ +- (NSArray *)argumentsFromStepName:(NSString *)stepName; + +/** + * Invokes a step given its name, using the given arguments. + * + * First derives the step matches. If it finds just one, invokes the + * step using the supplied arguments. + * Step definitions can invoke other step definitions as necessary. This method + * gives access to the steps @em by @em name. There could be more than one + * matching definition. There may even be no match. In such cases, the step + * invocation throws an exception. + */ +- (void)invokeWithArguments:(NSArray *)arguments; + +@end
diff --git a/OCCukes/OCCucumberStepDefinition.m b/OCCukes/OCCucumberStepDefinition.m new file mode 100755 index 0000000..a1f74b8 --- /dev/null +++ b/OCCukes/OCCucumberStepDefinition.m
@@ -0,0 +1,99 @@ +// OCCukes OCCucumberStepDefinition.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberStepDefinition.h" +#import "OCCucumberStepArgument.h" + +@implementation OCCucumberStepDefinition + +@synthesize regularExpression = _regularExpression; +@synthesize block = _block; +@synthesize file = _file; +@synthesize line = _line; + +- (id)initWithRegularExpression:(NSRegularExpression *)regularExpression block:(void (^)(NSArray *arguments))block +{ + if ((self = [self init])) + { + [self setRegularExpression:regularExpression]; + [self setBlock:block]; + } + return self; +} + +- (id)initWithPattern:(NSString *)pattern block:(void (^)(NSArray *arguments))block +{ + NSError *__autoreleasing error = nil; + NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; + // What to do with the error if the expression compiler raises one? + if (error) + { + [NSException raise:[error domain] format:@"%@", [error localizedDescription]]; + } + return regularExpression ? [self initWithRegularExpression:regularExpression block:block] : nil; +} + +- (NSString *)identifierString +{ + return [NSString stringWithFormat:@"%p", self]; +} + +- (NSArray *)argumentsFromStepName:(NSString *)stepName +{ + NSMutableArray *arguments; + NSTextCheckingResult *match = [[self regularExpression] firstMatchInString:stepName options:0 range:NSMakeRange(0, [stepName length])]; + if (match) + { + arguments = [NSMutableArray array]; + // The first range always captures the entire match; subsequent ranges + // match regular expression capture groups. + for (NSUInteger i = 1; i < [match numberOfRanges]; i++) + { + OCCucumberStepArgument *argument = [[OCCucumberStepArgument alloc] init]; + NSRange range = [match rangeAtIndex:i]; + // Only set up an argument's offset and value if the range is found. + // Optional captures may find nothing. + if (range.location != NSNotFound) + { + [argument setOffset:range.location]; + [argument setValue:[stepName substringWithRange:range]]; + } + [arguments addObject:argument]; + } + } + else + { + // Yes, you can send -copy to nil; [(NSObject *)nil copy] answers + // nil. In Objective-C, nil is a valid message receiver. + arguments = nil; + } + return [arguments copy]; +} + +- (void)invokeWithArguments:(NSArray *)arguments +{ + [self block](arguments); +} + +@end
diff --git a/OCCukes/OCCucumberStepMatch.h b/OCCukes/OCCucumberStepMatch.h new file mode 100755 index 0000000..cc71383 --- /dev/null +++ b/OCCukes/OCCucumberStepMatch.h
@@ -0,0 +1,36 @@ +// OCCukes OCCucumberStepMatch.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +@class OCCucumberStepDefinition; + +@interface OCCucumberStepMatch : NSObject + +@property(strong, NS_NONATOMIC_IOSONLY) OCCucumberStepDefinition *stepDefinition; +@property(strong, NS_NONATOMIC_IOSONLY) NSArray *stepArguments; + +- (id)initWithDefinition:(OCCucumberStepDefinition *)definition arguments:(NSArray *)arguments; + +@end
diff --git a/OCCukes/OCCucumberStepMatch.m b/OCCukes/OCCucumberStepMatch.m new file mode 100755 index 0000000..9af8d13 --- /dev/null +++ b/OCCukes/OCCucumberStepMatch.m
@@ -0,0 +1,42 @@ +// OCCukes OCCucumberStepMatch.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberStepMatch.h" + +@implementation OCCucumberStepMatch + +@synthesize stepDefinition = _stepDefinition; +@synthesize stepArguments = _stepArguments; + +- (id)initWithDefinition:(OCCucumberStepDefinition *)definition arguments:(NSArray *)arguments +{ + if ((self = [self init])) + { + [self setStepDefinition:definition]; + [self setStepArguments:arguments]; + } + return self; +} + +@end
diff --git a/OCCukes/OCCucumberWorld.h b/OCCukes/OCCucumberWorld.h new file mode 100755 index 0000000..a28995e --- /dev/null +++ b/OCCukes/OCCucumberWorld.h
@@ -0,0 +1,40 @@ +// OCCukes OCCucumberWorld.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <Foundation/Foundation.h> + +/** + * Scenarios create worlds. Worlds carry state for steps. When a scenario + * begins, Cucumber creates and sets up the current world. + * + * In Objective-C, worlds instantiate when scenarios begin. The OCCucumber + * language creates and connects it. In order to facilitate a similar paradigm + * within Objective-C, steps can also access the current world via the shared + * language. Each scenario's world acts as a vehicle for carrying values + * in-between steps. Use the key-value coding interface to set up and access + * your custom values. + */ +@interface OCCucumberWorld : NSObject + +@end
diff --git a/OCCukes/OCCucumberWorld.m b/OCCukes/OCCucumberWorld.m new file mode 100755 index 0000000..b5bc4bf --- /dev/null +++ b/OCCukes/OCCucumberWorld.m
@@ -0,0 +1,55 @@ +// OCCukes OCCucumberWorld.m +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "OCCucumberWorld.h" + +@interface OCCucumberWorld() + +@property(strong, NS_NONATOMIC_IOSONLY) NSMutableDictionary *values; + +@end + +@implementation OCCucumberWorld + +// designated initialiser +- (id)init +{ + if ((self = [super init])) + { + [self setValues:[NSMutableDictionary dictionary]]; + } + return self; +} + +- (void)setValue:(id)value forUndefinedKey:(NSString *)key +{ + [[self values] setObject:value forKey:key]; +} + +- (id)valueForUndefinedKey:(NSString *)key +{ + return [[self values] objectForKey:key]; +} + +@end
diff --git a/OCCukes/OCCukes.h b/OCCukes/OCCukes.h new file mode 100755 index 0000000..60a2a6e --- /dev/null +++ b/OCCukes/OCCukes.h
@@ -0,0 +1,31 @@ +// OCCukes OCCukes.h +// +// Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import <OCCukes/OCCucumber.h> +#import <OCCukes/OCCucumberRuntime.h> +#import <OCCukes/OCCucumberLanguage.h> +#import <OCCukes/OCCucumberWorld.h> +#import <OCCukes/OCCucumberStepDefinition.h> +#import <OCCukes/OCCucumberExceptions.h> +#import <OCCukes/Versioning.h>
diff --git a/OCCukes/Versioning.h b/OCCukes/Versioning.h new file mode 100755 index 0000000..246b13e --- /dev/null +++ b/OCCukes/Versioning.h
@@ -0,0 +1,36 @@ +/* OCCukes Versioning.h + * + * Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO + * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ******************************************************************************/ + +#import <Foundation/Foundation.h> + +extern const unsigned char kOCCukesVersionString[]; +extern const double kOCCukesVersionNumber; + +/** + * Answers the current Apple-generic versioning-formatted version string. The + * version string has been trimmed. It has no leading or trailing whitespace or + * newlines. Note that the raw C-based version string contrastingly has a single + * terminating newline character. + */ +NSString *OCCukesVersionString(void);
diff --git a/OCCukes/Versioning.m b/OCCukes/Versioning.m new file mode 100755 index 0000000..fb8e334 --- /dev/null +++ b/OCCukes/Versioning.m
@@ -0,0 +1,38 @@ +/* OCCukes Versioning.m + * + * Copyright © 2012, 2013, The OCCukes Organisation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO + * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + ******************************************************************************/ + +#import "Versioning.h" + +NSString *OCCukesVersionString() +{ + // The implementation assumes that the raw C-language version string + // terminates with null. It also trims assuming that the very last character + // is a terminating line feed. Also assumes UTF-8 encoding. + static NSString *__strong versionString; + if (versionString == nil) + { + versionString = [[NSString stringWithCString:(const char *)kOCCukesVersionString encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + } + return versionString; +}
diff --git a/README.md b/README.md new file mode 100755 index 0000000..3c9fe83 --- /dev/null +++ b/README.md
@@ -0,0 +1,286 @@ +# [](https://github.com/OCCukes/OCCukes) Objective-C Cucumber Wire Protocol + +[](https://travis-ci.org/royratcliffe/OCCukes) + +It allows Cucumber to touch your application in _intimate places_ (to quote +Cucumber's [wire protocol feature description][wire-protocol]). Goals include +and exclude: + +[wire-protocol]:https://github.com/cucumber/cucumber/blob/master/legacy_features/wire_protocol_erb.feature + +* Implement the Cucumber wire protocol. + + This is the most direct way to connect Cucumber to non-Ruby environments. + +* Not to link against the C++ standard library. + + Just pure Automatic Reference Counting (ARC) Objective-C based on Apple's + Foundation framework. + +Why the OC name-space prefix? OC stands for Objective-C. It emphasises the +underlying dependency as well as the multiplatform capability. OCCukes supports +Objective-C on all Apple platforms: iOS and OS X. + +The project does _not_ include an expectation framework. It only runs +Objective-C coded step definitions. Your step definitions must assert +appropriate expectations, possibly by running _other_ step definitions. Since +you write your Cucumber step definitions in Objective-C, you can use any kind +of assertion framework, or even write your own. Exceptions thrown by the step +become Cucumber step failures. See +[OCExpectations](https://github.com/OCCukes/OCExpectations) for an expectations +library. + +OCCukes and its companion OCExpectations sails as close to Cucumber and RSpec +shores as is possible. If you are familiar with Cucumber and RSpec, you should +find these projects refreshingly familiar; despite the differing implementation +languages. Interfaces and implementations mirror their Ruby counterparts. + +To use OCCukes, you need Xcode 4.4 or above, Ruby as well as Apple's Command +Line Tools package. Install them on your Mac first. See +[Prerequisites](https://github.com/OCCukes/OCCukes/wiki/Prerequisites) for +details. + +## Usage + +OCCukes integrates with Xcode. You launch a Cucumber-based test suite along +with any other Xcode project test suite: just press Command+U. Cucumber +piggy-backs on the standard SenTestKit (OCUnit) tests. + +To make this work, you need to launch Cucumber in the background while your +test suite runs. Xcode's pre-actions let you do this. To set up the pre- and +post-actions for your test target, just install Cucumber using RVM. + +### Test scheme pre-action + +Make this your pre-action for the Test scheme: + + PATH=$PATH:$HOME/.rvm/bin + rvm 1.9.3 do cucumber "$SRCROOT/features" --format html --out "$OBJROOT/features.html" + +It sets up the PATH variable so that the shell can find RVM; Xcode resets the +PATH when shelling out. It then launches Cucumber using RVM with Ruby 1.9.3; +this looks for the latest 1.9.3-version of Ruby installed, but quietly fails if +the latest version is __not__ installed. Success assumes you have already +installed Cucumber in the Ruby 1.9.3 RVM; adjust according to your local +environment and personal preferences. Use `gem install cucumber dnssd` within +the selected Ruby version to install Cucumber and its dependencies. + +### Test scheme post-action + +Then make this your post-action: + + open "$OBJROOT/features.html" + +### Wire protocol configuration + +Add a wire configuration to your `features/step_definitions` folder, a YAML +file with a `.wire` extension. Contents as follows. + + host: _occukes-runtime._tcp. + + # The default three-second time-out might not help a debugging + # effort. Instead, lengthen the timeouts for specific wire protocol + # messages. + timeout: + step_matches: 120 + +Host and port describe where to find the wire socket service. If you want to +use Bonjour (DNS Service Discovery, DNSSD) to resolve the host address and the +port number, specify a DNS service name as the host. This triggers Bonjour +discovery. The Cucumber wire service accepts connections at port 54321 on _any_ +interface. So you can connect to non-local hosts as well. + +You can override the Cucumber runtime connect and disconnect timeouts +at the command line. For example, use + + defaults write org.OCCukes OCCucumberRuntimeDisconnectTimeout -float 120.0 + +to reconfigure the disconnect timeout to two minutes. Express +timeouts in units of seconds. Display the current configuration using + + defaults read org.OCCukes + +### Environment Support + +[env-rb]:https://github.com/OCCukes/OCCukes/blob/master/features/support/env.rb + +Set up your `features/support/env.rb`. You can copy this code from +[`features/support/env.rb`][env-rb]. The Ruby code defines a Cucumber +`AfterConfiguration` block for daemonising the Cucumber process and waiting for +the wire server to begin accepting socket connections. This block runs after +Cucumber configuration. + +### Add test case + +Finally, integrate Cucumber tests with your standard unit test cases by adding +steps. + +Basic template for some step definitions, `MySteps.m`: + + #import <OCCukes/OCCukes.h> + + __attribute__((constructor)) + static void StepDefinitions() + { + + } + +Make as many such step definition modules as required. Organise the steps +around related features. + +Register your step definitions before executing the Objective-C Cucumber +runtime by sending `-run`. As you see above, definitions manifest themselves in +Objective-C as C blocks. These blocks assert the step's expectations, throwing +an exception if any expectations fail. Steps therefore succeed when they +encounter no exceptions. + +The runtime accepts connections until all connections disappear. By default, it +offers an initial 10-second connection window; giving up if Cucumber fails to +connect for 10 seconds. The runtime also offers a 1-second disconnection window +before shutting down the socket. This allows all connections to disappear +temporarily. You can adjust these connect and disconnect timeouts if necessary. + +Link your test target against the `OCCukes.framework` for OS X platforms; or +against the `libOCCukes.a` static library for iOS test targets. For iOS +targets, you also need `OTHER_LDFLAGS` equal to `-all_load`; the linker does +not automatically load Objective-C categories when they appear in a static +library but this flag forces it to. + +## Advantages + +Why use OCCukes? + +### Test bundle injection + +Takes advantage of Apple's test bundle injection mechanism. In other words, you +do not need to link your target against some other library. Your target needs +nothing extra. This obviates any need for maintaining multiple targets, one for +the application proper, then another one for Cucumber testing. Xcode takes care +of the injection of the wire protocol server along with all other tests and +dependencies at testing time. + +This prevents a proliferation of targets, making project maintenance easier. +You do not need to have a Cucumber'ified target which duplicates your target +proper but adds additional dependencies. Bundle injection handles all that for +you. One application, one target. + +### No additional Ruby + +The OCCukes approach obviates any additional Ruby-side client gem needed for +bridging work between Cucumber and iOS or OS X. Cucumber is the direct client +end-point. It already contains the necessary equipment for talking to OCCukes. +No need for another adapter. OCCukes talks _native_ Cucumber. + +This also means that you do not need to build and maintain a skeletal structure +within your features just for adapting and connecting to a remote test system. +Cuts out the [cruft](http://foldoc.org/cruft). + +### No private dependencies + +The software only makes use of public APIs. This makes it far less brittle. +Private frameworks can and do change without notice. Projects relying on them +can easily become redundant especially as Apple's operating systems advance +rapidly. + +### Multiple subprojects + +The OCCukes organisation publishes [various Cucumber-related subprojects](https://github.com/OCCukes/OCCukes/wiki/Repos). +Although the OCCukes project lies at the core, complementary projects +OCExpectations and a slew of iOS-specific spinoffs exist: UICukes, +UIExpectations and UIAutomation. The structure helps to avoid an all-or-nothing +mindset. Take whatever best suits your needs. The projects aim at various kinds +of development projects: iOS application, Mac application, iOS library, or Mac +framework. + +OCCukes sub-projects prefixed by `OC` have Objective-C and Foundation framework +dependencies. That means they work on iOS _and_ OS X platforms. They are +cross-platform projects and incorporate iOS library targets as well as OS X +framework targets. + +Projects prefixed by `UI` have iOS UIKit dependencies. They aim at iOS projects +only. Their Xcode projects contain a single iOS static library target. +[UICukes](https://github.com/OCCukes/UICukes) acts as an umbrella project for +iOS dependencies. It pulls in all other sub-projects needed for Cucumber on +iOS. iOS developers will therefore normally clone out the UICukes submodule by +itself. Doing so pulls in all other dependencies as sub-submodules. + +## Troubleshooting + +### Cucumber launches but invokes no steps + +You press Cmd+U in Xcode to run your tests. There is a brief pause then you see +the Cucumber output. Cucumber has executed and parsed the features and +scenarios. Trouble is, Cucumber stops at the first scenario's first step. No +steps execute. When you set a breakpoint within your step definitions, sure +enough, they never run. + +#### Solution + +Make sure that you are not running a Cucumber instance in some other process. +For example, you might be running it as part of an IDE for some other project, +or some other components of the same project. Terminate the other Cucumbers and +re-test. + +## When and how to launch Cucumber? + +Running Cucumber with Mac or iOS software requires two synchronised processes: +a Cucumber client in Ruby, and an Objective-C wire server running within an +application test bundle. The server needs to run first in order to open a wire +server socket. The socket may be local or on another device. Actual iOS devices +do not share the same local host. Hence the Cucumber client cannot assume +`localhost`. + +Wire server and client need to synchronise their execution. When Cucumber runs, +it expects to connect to the wire server when it finds a wire configuration. +Hence the Test scheme needs to launch the server beforehand. Best way to launch +Cucumber from Xcode: use a pre-action within the Test scheme. Cucumber +therefore needs to do three things at launch time: + +1. fork itself into the background so that Xcode testing can proceed, that is, daemonise; +2. wait for the wire server to set itself up; +3. close down the wire server when Cucumber finishes running its features and + invoking the remote-wire step definitions. This can happen automatically when + it server sees no wire connection after a given period of time, say a second. + +Step 2 raises issues when Cucumber tests actual iOS devices where the server +does not open a port on `localhost`. Waiting for the wire server to initialise +(step 2) requires that the Cucumber run-time environment establishes the wire +server's address and port information. Without it, the Cucumber run will fail. +The environment needs to wait an acceptable time for the server to appear on +the prescribed port. Moreover, the client-side environment needs to attempt and +reattempt to connect. The initial attempts will likely fail with a "refuse to +connect" exception, simply because the server has not yet opened the socket. +Steps 2 and 3 together imply a setting up and tearing down at the Cucumber +client side. + +## MIT Licensing + +Copyright © 2012, 2013 The OCCukes Organisation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +## Sponsors + +- Levide Capital Limited, Blenheim, New Zealand + +## Contributors + +- Roy Ratcliffe, Pioneering Software, United Kingdom +- Bennett Smith, Focal Shift LLC, United States +- Terry Tucker, Focal Shift LLC, United States